diff options
488 files changed, 33824 insertions, 8815 deletions
diff --git a/Documentation/admin-guide/media/cec-drivers.rst b/Documentation/admin-guide/media/cec-drivers.rst deleted file mode 100644 index 8d9686c08df9..000000000000 --- a/Documentation/admin-guide/media/cec-drivers.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -================================= -CEC driver-specific documentation -================================= - -.. toctree:: - :maxdepth: 2 - - pulse8-cec diff --git a/Documentation/admin-guide/media/cec.rst b/Documentation/admin-guide/media/cec.rst new file mode 100644 index 000000000000..5c7259371494 --- /dev/null +++ b/Documentation/admin-guide/media/cec.rst @@ -0,0 +1,369 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======== +HDMI CEC +======== + +Supported hardware in mainline +============================== + +HDMI Transmitters: + +- Exynos4 +- Exynos5 +- STIH4xx HDMI CEC +- V4L2 adv7511 (same HW, but a different driver from the drm adv7511) +- stm32 +- Allwinner A10 (sun4i) +- Raspberry Pi +- dw-hdmi (Synopsis IP) +- amlogic (meson ao-cec and ao-cec-g12a) +- drm adv7511/adv7533 +- omap4 +- tegra +- rk3288, rk3399 +- tda998x +- DisplayPort CEC-Tunneling-over-AUX on i915, nouveau and amdgpu +- ChromeOS EC CEC +- CEC for SECO boards (UDOO x86). +- Chrontel CH7322 + + +HDMI Receivers: + +- adv7604/11/12 +- adv7842 +- tc358743 + +USB Dongles (see below for additional information on how to use these +dongles): + +- Pulse-Eight: the pulse8-cec driver implements the following module option: + ``persistent_config``: by default this is off, but when set to 1 the driver + will store the current settings to the device's internal eeprom and restore + it the next time the device is connected to the USB port. +- RainShadow Tech. Note: this driver does not support the persistent_config + module option of the Pulse-Eight driver. The hardware supports it, but I + have no plans to add this feature. But I accept patches :-) + +Miscellaneous: + +- vivid: emulates a CEC receiver and CEC transmitter. + Can be used to test CEC applications without actual CEC hardware. + +- cec-gpio. If the CEC pin is hooked up to a GPIO pin then + you can control the CEC line through this driver. This supports error + injection as well. + + +Utilities +========= + +Utilities are available here: https://git.linuxtv.org/v4l-utils.git + +``utils/cec-ctl``: control a CEC device + +``utils/cec-compliance``: test compliance of a remote CEC device + +``utils/cec-follower``: emulate a CEC follower device + +Note that ``cec-ctl`` has support for the CEC Hospitality Profile as is +used in some hotel displays. See http://www.htng.org. + +Note that the libcec library (https://github.com/Pulse-Eight/libcec) supports +the linux CEC framework. + +If you want to get the CEC specification, then look at the References of +the HDMI wikipedia page: https://en.wikipedia.org/wiki/HDMI. CEC is part +of the HDMI specification. HDMI 1.3 is freely available (very similar to +HDMI 1.4 w.r.t. CEC) and should be good enough for most things. + + +DisplayPort to HDMI Adapters with working CEC +============================================= + +Background: most adapters do not support the CEC Tunneling feature, +and of those that do many did not actually connect the CEC pin. +Unfortunately, this means that while a CEC device is created, it +is actually all alone in the world and will never be able to see other +CEC devices. + +This is a list of known working adapters that have CEC Tunneling AND +that properly connected the CEC pin. If you find adapters that work +but are not in this list, then drop me a note. + +To test: hook up your DP-to-HDMI adapter to a CEC capable device +(typically a TV), then run:: + + cec-ctl --playback # Configure the PC as a CEC Playback device + cec-ctl -S # Show the CEC topology + +The ``cec-ctl -S`` command should show at least two CEC devices, +ourselves and the CEC device you are connected to (i.e. typically the TV). + +General note: I have only seen this work with the Parade PS175, PS176 and +PS186 chipsets and the MegaChips 2900. While MegaChips 28x0 claims CEC support, +I have never seen it work. + +USB-C to HDMI +------------- + +Samsung Multiport Adapter EE-PW700: https://www.samsung.com/ie/support/model/EE-PW700BBEGWW/ + +Kramer ADC-U31C/HF: https://www.kramerav.com/product/ADC-U31C/HF + +Club3D CAC-2504: https://www.club-3d.com/en/detail/2449/usb_3.1_type_c_to_hdmi_2.0_uhd_4k_60hz_active_adapter/ + +DisplayPort to HDMI +------------------- + +Club3D CAC-1080: https://www.club-3d.com/en/detail/2442/displayport_1.4_to_hdmi_2.0b_hdr/ + +CableCreation (SKU: CD0712): https://www.cablecreation.com/products/active-displayport-to-hdmi-adapter-4k-hdr + +HP DisplayPort to HDMI True 4k Adapter (P/N 2JA63AA): https://www.hp.com/us-en/shop/pdp/hp-displayport-to-hdmi-true-4k-adapter + +Mini-DisplayPort to HDMI +------------------------ + +Club3D CAC-1180: https://www.club-3d.com/en/detail/2443/mini_displayport_1.4_to_hdmi_2.0b_hdr/ + +Note that passive adapters will never work, you need an active adapter. + +The Club3D adapters in this list are all MegaChips 2900 based. Other Club3D adapters +are PS176 based and do NOT have the CEC pin hooked up, so only the three Club3D +adapters above are known to work. + +I suspect that MegaChips 2900 based designs in general are likely to work +whereas with the PS176 it is more hit-and-miss (mostly miss). The PS186 is +likely to have the CEC pin hooked up, it looks like they changed the reference +design for that chipset. + + +USB CEC Dongles +=============== + +These dongles appear as ``/dev/ttyACMX`` devices and need the ``inputattach`` +utility to create the ``/dev/cecX`` devices. Support for the Pulse-Eight +has been added to ``inputattach`` 1.6.0. Support for the Rainshadow Tech has +been added to ``inputattach`` 1.6.1. + +You also need udev rules to automatically start systemd services:: + + SUBSYSTEM=="tty", KERNEL=="ttyACM[0-9]*", ATTRS{idVendor}=="2548", ATTRS{idProduct}=="1002", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="pulse8-cec-inputattach@%k.service" + SUBSYSTEM=="tty", KERNEL=="ttyACM[0-9]*", ATTRS{idVendor}=="2548", ATTRS{idProduct}=="1001", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="pulse8-cec-inputattach@%k.service" + SUBSYSTEM=="tty", KERNEL=="ttyACM[0-9]*", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="ff59", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rainshadow-cec-inputattach@%k.service" + +and these systemd services: + +For Pulse-Eight make /lib/systemd/system/pulse8-cec-inputattach@.service:: + + [Unit] + Description=inputattach for pulse8-cec device on %I + + [Service] + Type=simple + ExecStart=/usr/bin/inputattach --pulse8-cec /dev/%I + +For the RainShadow Tech make /lib/systemd/system/rainshadow-cec-inputattach@.service:: + + [Unit] + Description=inputattach for rainshadow-cec device on %I + + [Service] + Type=simple + ExecStart=/usr/bin/inputattach --rainshadow-cec /dev/%I + + +For proper suspend/resume support create: /lib/systemd/system/restart-cec-inputattach.service:: + + [Unit] + Description=restart inputattach for cec devices + After=suspend.target + + [Service] + Type=forking + ExecStart=/bin/bash -c 'for d in /dev/serial/by-id/usb-Pulse-Eight*; do /usr/bin/inputattach --daemon --pulse8-cec $d; done; for d in /dev/serial/by-id/usb-RainShadow_Tech*; do /usr/bin/inputattach --daemon --rainshadow-cec $d; done' + + [Install] + WantedBy=suspend.target + +And run ``systemctl enable restart-cec-inputattach``. + +To automatically set the physical address of the CEC device whenever the +EDID changes, you can use ``cec-ctl`` with the ``-E`` option:: + + cec-ctl -E /sys/class/drm/card0-DP-1/edid + +This assumes the dongle is connected to the card0-DP-1 output (``xrandr`` will tell +you which output is used) and it will poll for changes to the EDID and update +the Physical Address whenever they occur. + +To automatically run this command you can use cron. Edit crontab with +``crontab -e`` and add this line:: + + @reboot /usr/local/bin/cec-ctl -E /sys/class/drm/card0-DP-1/edid + +This only works for display drivers that expose the EDID in ``/sys/class/drm``, +such as the i915 driver. + + +CEC Without HPD +=============== + +Some displays when in standby mode have no HDMI Hotplug Detect signal, but +CEC is still enabled so connected devices can send an <Image View On> CEC +message in order to wake up such displays. Unfortunately, not all CEC +adapters can support this. An example is the Odroid-U3 SBC that has a +level-shifter that is powered off when the HPD signal is low, thus +blocking the CEC pin. Even though the SoC can use CEC without a HPD, +the level-shifter will prevent this from functioning. + +There is a CEC capability flag to signal this: ``CEC_CAP_NEEDS_HPD``. +If set, then the hardware cannot wake up displays with this behavior. + +Note for CEC application implementers: the <Image View On> message must +be the first message you send, don't send any other messages before. +Certain very bad but unfortunately not uncommon CEC implementations +get very confused if they receive anything else but this message and +they won't wake up. + +When writing a driver it can be tricky to test this. There are two +ways to do this: + +1) Get a Pulse-Eight USB CEC dongle, connect an HDMI cable from your + device to the Pulse-Eight, but do not connect the Pulse-Eight to + the display. + + Now configure the Pulse-Eight dongle:: + + cec-ctl -p0.0.0.0 --tv + + and start monitoring:: + + sudo cec-ctl -M + + On the device you are testing run:: + + cec-ctl --playback + + It should report a physical address of f.f.f.f. Now run this + command:: + + cec-ctl -t0 --image-view-on + + The Pulse-Eight should see the <Image View On> message. If not, + then something (hardware and/or software) is preventing the CEC + message from going out. + + To make sure you have the wiring correct just connect the + Pulse-Eight to a CEC-enabled display and run the same command + on your device: now there is a HPD, so you should see the command + arriving at the Pulse-Eight. + +2) If you have another linux device supporting CEC without HPD, then + you can just connect your device to that device. Yes, you can connect + two HDMI outputs together. You won't have a HPD (which is what we + want for this test), but the second device can monitor the CEC pin. + + Otherwise use the same commands as in 1. + +If CEC messages do not come through when there is no HPD, then you +need to figure out why. Typically it is either a hardware restriction +or the software powers off the CEC core when the HPD goes low. The +first cannot be corrected of course, the second will likely required +driver changes. + + +Microcontrollers & CEC +====================== + +We have seen some CEC implementations in displays that use a microcontroller +to sample the bus. This does not have to be a problem, but some implementations +have timing issues. This is hard to discover unless you can hook up a low-level +CEC debugger (see the next section). + +You will see cases where the CEC transmitter holds the CEC line high or low for +a longer time than is allowed. For directed messages this is not a problem since +if that happens the message will not be Acked and it will be retransmitted. +For broadcast messages no such mechanism exists. + +It's not clear what to do about this. It is probably wise to transmit some +broadcast messages twice to reduce the chance of them being lost. Specifically +<Standby> and <Active Source> are candidates for that. + + +Making a CEC debugger +===================== + +By using a Raspberry Pi 2B/3/4 and some cheap components you can make +your own low-level CEC debugger. + +Here is a picture of my setup: + +https://hverkuil.home.xs4all.nl/rpi3-cec.jpg + +It's a Raspberry Pi 3 together with a breadboard and some breadboard wires: + +http://www.dx.com/p/diy-40p-male-to-female-male-to-male-female-to-female-dupont-line-wire-3pcs-356089#.WYLOOXWGN7I + +Finally on of these HDMI female-female passthrough connectors (full soldering type 1): + +https://elabbay.myshopify.com/collections/camera/products/hdmi-af-af-v1a-hdmi-type-a-female-to-hdmi-type-a-female-pass-through-adapter-breakout-board?variant=45533926147 + +We've tested this and it works up to 4kp30 (297 MHz). The quality is not high +enough to pass-through 4kp60 (594 MHz). + +I also added an RTC and a breakout shield: + +https://www.amazon.com/Makerfire%C2%AE-Raspberry-Module-DS1307-Battery/dp/B00ZOXWHK4 + +https://www.dx.com/p/raspberry-pi-gpio-expansion-board-breadboard-easy-multiplexing-board-one-to-three-with-screw-for-raspberry-pi-2-3-b-b-2729992.html#.YGRCG0MzZ7I + +These two are not needed but they make life a bit easier. + +If you want to monitor the HPD line as well, then you need one of these +level shifters: + +https://www.adafruit.com/product/757 + +(This is just where I got these components, there are many other places you +can get similar things). + +The CEC pin of the HDMI connector needs to be connected to these pins: +CE0/IO8 and CE1/IO7 (pull-up GPIOs). The (optional) HPD pin of the HDMI +connector should be connected (via a level shifter to convert the 5V +to 3.3V) to these pins: IO17 and IO27. The (optional) 5V pin of the HDMI +connector should be connected (via a level shifter) to these pins: IO22 +and IO24. Monitoring the HPD an 5V lines is not necessary, but it is helpful. + +This kernel patch will hook up the cec-gpio driver correctly to +e.g. ``arch/arm/boot/dts/bcm2837-rpi-3-b-plus.dts``:: + + cec-gpio@7 { + compatible = "cec-gpio"; + cec-gpios = <&gpio 7 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + hpd-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>; + v5-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>; + }; + + cec-gpio@8 { + compatible = "cec-gpio"; + cec-gpios = <&gpio 8 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + hpd-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; + v5-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>; + }; + +This dts change will enable two cec GPIO devices: I typically use one to +send/receive CEC commands and the other to monitor. If you monitor using +an unconfigured CEC adapter then it will use GPIO interrupts which makes +monitoring very accurate. + +The documentation on how to use the error injection is here: :ref:`cec_pin_error_inj`. + +``cec-ctl --monitor-pin`` will do low-level CEC bus sniffing and analysis. +You can also store the CEC traffic to file using ``--store-pin`` and analyze +it later using ``--analyze-pin``. + +You can also use this as a full-fledged CEC device by configuring it +using ``cec-ctl --tv -p0.0.0.0`` or ``cec-ctl --playback -p1.0.0.0``. diff --git a/Documentation/admin-guide/media/index.rst b/Documentation/admin-guide/media/index.rst index c676af665111..43f4a292b245 100644 --- a/Documentation/admin-guide/media/index.rst +++ b/Documentation/admin-guide/media/index.rst @@ -38,13 +38,14 @@ The media subsystem remote-controller + cec + dvb cardlist v4l-drivers dvb-drivers - cec-drivers **Copyright** |copy| 1999-2020 : LinuxTV Developers diff --git a/Documentation/admin-guide/media/pulse8-cec.rst b/Documentation/admin-guide/media/pulse8-cec.rst deleted file mode 100644 index 356d08b519f3..000000000000 --- a/Documentation/admin-guide/media/pulse8-cec.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -Pulse-Eight CEC Adapter driver -============================== - -The pulse8-cec driver implements the following module option: - -``persistent_config`` ---------------------- - -By default this is off, but when set to 1 the driver will store the current -settings to the device's internal eeprom and restore it the next time the -device is connected to the USB port. diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst index 9c7ebe2ca3bd..90a026ee05c6 100644 --- a/Documentation/admin-guide/media/v4l-drivers.rst +++ b/Documentation/admin-guide/media/v4l-drivers.rst @@ -31,4 +31,5 @@ Video4Linux (V4L) driver-specific documentation si4713 si476x vimc + visl vivid diff --git a/Documentation/admin-guide/media/vimc.rst b/Documentation/admin-guide/media/vimc.rst index 3b4d2b36b4f3..29d843a8ddb1 100644 --- a/Documentation/admin-guide/media/vimc.rst +++ b/Documentation/admin-guide/media/vimc.rst @@ -35,11 +35,11 @@ of commands fits for the default topology: media-ctl -d platform:vimc -V '"Sensor A":0[fmt:SBGGR8_1X8/640x480]' media-ctl -d platform:vimc -V '"Debayer A":0[fmt:SBGGR8_1X8/640x480]' - media-ctl -d platform:vimc -V '"Sensor B":0[fmt:SBGGR8_1X8/640x480]' - media-ctl -d platform:vimc -V '"Debayer B":0[fmt:SBGGR8_1X8/640x480]' - v4l2-ctl -z platform:vimc -d "RGB/YUV Capture" -v width=1920,height=1440 + media-ctl -d platform:vimc -V '"Scaler":0[fmt:RGB888_1X24/640x480]' + media-ctl -d platform:vimc -V '"Scaler":0[crop:(100,50)/400x150]' + media-ctl -d platform:vimc -V '"Scaler":1[fmt:RGB888_1X24/300x700]' + v4l2-ctl -z platform:vimc -d "RGB/YUV Capture" -v width=300,height=700 v4l2-ctl -z platform:vimc -d "Raw Capture 0" -v pixelformat=BA81 - v4l2-ctl -z platform:vimc -d "Raw Capture 1" -v pixelformat=BA81 Subdevices ---------- diff --git a/Documentation/admin-guide/media/visl.rst b/Documentation/admin-guide/media/visl.rst new file mode 100644 index 000000000000..7d2dc78341c9 --- /dev/null +++ b/Documentation/admin-guide/media/visl.rst @@ -0,0 +1,175 @@ +.. SPDX-License-Identifier: GPL-2.0 + +The Virtual Stateless Decoder Driver (visl) +=========================================== + +A virtual stateless decoder device for stateless uAPI development +purposes. + +This tool's objective is to help the development and testing of +userspace applications that use the V4L2 stateless API to decode media. + +A userspace implementation can use visl to run a decoding loop even when +no hardware is available or when the kernel uAPI for the codec has not +been upstreamed yet. This can reveal bugs at an early stage. + +This driver can also trace the contents of the V4L2 controls submitted +to it. It can also dump the contents of the vb2 buffers through a +debugfs interface. This is in many ways similar to the tracing +infrastructure available for other popular encode/decode APIs out there +and can help develop a userspace application by using another (working) +one as a reference. + +.. note:: + + No actual decoding of video frames is performed by visl. The + V4L2 test pattern generator is used to write various debug information + to the capture buffers instead. + +Module parameters +----------------- + +- visl_debug: Activates debug info, printing various debug messages through + dprintk. Also controls whether per-frame debug info is shown. Defaults to off. + Note that enabling this feature can result in slow performance through serial. + +- visl_transtime_ms: Simulated process time in milliseconds. Slowing down the + decoding speed can be useful for debugging. + +- visl_dprintk_frame_start, visl_dprintk_frame_nframes: Dictates a range of + frames where dprintk is activated. This only controls the dprintk tracing on a + per-frame basis. Note that printing a lot of data can be slow through serial. + +- keep_bitstream_buffers: Controls whether bitstream (i.e. OUTPUT) buffers are + kept after a decoding session. Defaults to false so as to reduce the amount of + clutter. keep_bitstream_buffers == false works well when live debugging the + client program with GDB. + +- bitstream_trace_frame_start, bitstream_trace_nframes: Similar to + visl_dprintk_frame_start, visl_dprintk_nframes, but controls the dumping of + buffer data through debugfs instead. + +What is the default use case for this driver? +--------------------------------------------- + +This driver can be used as a way to compare different userspace implementations. +This assumes that a working client is run against visl and that the ftrace and +OUTPUT buffer data is subsequently used to debug a work-in-progress +implementation. + +Information on reference frames, their timestamps, the status of the OUTPUT and +CAPTURE queues and more can be read directly from the CAPTURE buffers. + +Supported codecs +---------------- + +The following codecs are supported: + +- FWHT +- MPEG2 +- VP8 +- VP9 +- H.264 +- HEVC + +visl trace events +----------------- +The trace events are defined on a per-codec basis, e.g.: + +.. code-block:: bash + + $ ls /sys/kernel/debug/tracing/events/ | grep visl + visl_fwht_controls + visl_h264_controls + visl_hevc_controls + visl_mpeg2_controls + visl_vp8_controls + visl_vp9_controls + +For example, in order to dump HEVC SPS data: + +.. code-block:: bash + + $ echo 1 > /sys/kernel/debug/tracing/events/visl_hevc_controls/v4l2_ctrl_hevc_sps/enable + +The SPS data will be dumped to the trace buffer, i.e.: + +.. code-block:: bash + + $ cat /sys/kernel/debug/tracing/trace + video_parameter_set_id 0 + seq_parameter_set_id 0 + pic_width_in_luma_samples 1920 + pic_height_in_luma_samples 1080 + bit_depth_luma_minus8 0 + bit_depth_chroma_minus8 0 + log2_max_pic_order_cnt_lsb_minus4 4 + sps_max_dec_pic_buffering_minus1 6 + sps_max_num_reorder_pics 2 + sps_max_latency_increase_plus1 0 + log2_min_luma_coding_block_size_minus3 0 + log2_diff_max_min_luma_coding_block_size 3 + log2_min_luma_transform_block_size_minus2 0 + log2_diff_max_min_luma_transform_block_size 3 + max_transform_hierarchy_depth_inter 2 + max_transform_hierarchy_depth_intra 2 + pcm_sample_bit_depth_luma_minus1 0 + pcm_sample_bit_depth_chroma_minus1 0 + log2_min_pcm_luma_coding_block_size_minus3 0 + log2_diff_max_min_pcm_luma_coding_block_size 0 + num_short_term_ref_pic_sets 0 + num_long_term_ref_pics_sps 0 + chroma_format_idc 1 + sps_max_sub_layers_minus1 0 + flags AMP_ENABLED|SAMPLE_ADAPTIVE_OFFSET|TEMPORAL_MVP_ENABLED|STRONG_INTRA_SMOOTHING_ENABLED + + +Dumping OUTPUT buffer data through debugfs +------------------------------------------ + +If the **VISL_DEBUGFS** Kconfig is enabled, visl will populate +**/sys/kernel/debug/visl/bitstream** with OUTPUT buffer data according to the +values of bitstream_trace_frame_start and bitstream_trace_nframes. This can +highlight errors as broken clients may fail to fill the buffers properly. + +A single file is created for each processed OUTPUT buffer. Its name contains an +integer that denotes the buffer sequence, i.e.: + +.. code-block:: c + + snprintf(name, 32, "bitstream%d", run->src->sequence); + +Dumping the values is simply a matter of reading from the file, i.e.: + +For the buffer with sequence == 0: + +.. code-block:: bash + + $ xxd /sys/kernel/debug/visl/bitstream/bitstream0 + 00000000: 2601 af04 d088 bc25 a173 0e41 a4f2 3274 &......%.s.A..2t + 00000010: c668 cb28 e775 b4ac f53a ba60 f8fd 3aa1 .h.(.u...:.`..:. + 00000020: 46b4 bcfc 506c e227 2372 e5f5 d7ea 579f F...Pl.'#r....W. + 00000030: 6371 5eb5 0eb8 23b5 ca6a 5de5 983a 19e4 cq^...#..j]..:.. + 00000040: e8c3 4320 b4ba a226 cbc1 4138 3a12 32d6 ..C ...&..A8:.2. + 00000050: fef3 247b 3523 4e90 9682 ac8e eb0c a389 ..${5#N......... + 00000060: ddd0 6cfc 0187 0e20 7aae b15b 1812 3d33 ..l.... z..[..=3 + 00000070: e1c5 f425 a83a 00b7 4f18 8127 3c4c aefb ...%.:..O..'<L.. + +For the buffer with sequence == 1: + +.. code-block:: bash + + $ xxd /sys/kernel/debug/visl/bitstream/bitstream1 + 00000000: 0201 d021 49e1 0c40 aa11 1449 14a6 01dc ...!I..@...I.... + 00000010: 7023 889a c8cd 2cd0 13b4 dab0 e8ca 21fe p#....,.......!. + 00000020: c4c8 ab4c 486e 4e2f b0df 96cc c74e 8dde ...LHnN/.....N.. + 00000030: 8ce7 ee36 d880 4095 4d64 30a0 ff4f 0c5e ...6..@.Md0..O.^ + 00000040: f16b a6a1 d806 ca2a 0ece a673 7bea 1f37 .k.....*...s{..7 + 00000050: 370f 5bb9 1dc4 ba21 6434 bc53 0173 cba0 7.[....!d4.S.s.. + 00000060: dfe6 bc99 01ea b6e0 346b 92b5 c8de 9f5d ........4k.....] + 00000070: e7cc 3484 1769 fef2 a693 a945 2c8b 31da ..4..i.....E,.1. + +And so on. + +By default, the files are removed during STREAMOFF. This is to reduce the amount +of clutter. diff --git a/Documentation/admin-guide/media/vivid.rst b/Documentation/admin-guide/media/vivid.rst index abd90ed31090..672a8371f6ad 100644 --- a/Documentation/admin-guide/media/vivid.rst +++ b/Documentation/admin-guide/media/vivid.rst @@ -392,7 +392,7 @@ Which one is returned depends on the chosen channel, each next valid channel will cycle through the possible audio subchannel combinations. This allows you to test the various combinations by just switching channels.. -Finally, for these inputs the v4l2_timecode struct is filled in in the +Finally, for these inputs the v4l2_timecode struct is filled in the dequeued v4l2_buffer struct. diff --git a/Documentation/devicetree/bindings/media/allwinner,sun6i-a31-csi.yaml b/Documentation/devicetree/bindings/media/allwinner,sun6i-a31-csi.yaml index f1ccca35a790..b3d6db922693 100644 --- a/Documentation/devicetree/bindings/media/allwinner,sun6i-a31-csi.yaml +++ b/Documentation/devicetree/bindings/media/allwinner,sun6i-a31-csi.yaml @@ -73,6 +73,10 @@ properties: $ref: /schemas/graph.yaml#/properties/port description: MIPI CSI-2 bridge input port + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Internal output port to the ISP + anyOf: - required: - port@0 diff --git a/Documentation/devicetree/bindings/media/allwinner,sun6i-a31-isp.yaml b/Documentation/devicetree/bindings/media/allwinner,sun6i-a31-isp.yaml new file mode 100644 index 000000000000..6bda4f2b94c2 --- /dev/null +++ b/Documentation/devicetree/bindings/media/allwinner,sun6i-a31-isp.yaml @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/allwinner,sun6i-a31-isp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Allwinner A31 Image Signal Processor Driver (ISP) Device Tree Bindings + +maintainers: + - Paul Kocialkowski <paul.kocialkowski@bootlin.com> + +properties: + compatible: + enum: + - allwinner,sun6i-a31-isp + - allwinner,sun8i-v3s-isp + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Bus Clock + - description: Module Clock + - description: DRAM Clock + + clock-names: + items: + - const: bus + - const: mod + - const: ram + + resets: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: CSI0 input port + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: CSI1 input port + + if: + properties: + compatible: + contains: + enum: + - allwinner,sun8i-v3s-isp + then: + required: + - port@0 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/clock/sun8i-v3s-ccu.h> + #include <dt-bindings/reset/sun8i-v3s-ccu.h> + + isp: isp@1cb8000 { + compatible = "allwinner,sun8i-v3s-isp"; + reg = <0x01cb8000 0x1000>; + interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&ccu CLK_BUS_CSI>, + <&ccu CLK_CSI1_SCLK>, + <&ccu CLK_DRAM_CSI>; + clock-names = "bus", "mod", "ram"; + resets = <&ccu RST_BUS_CSI>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + isp_in_csi0: endpoint { + remote-endpoint = <&csi0_out_isp>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/i2c/imx290.txt b/Documentation/devicetree/bindings/media/i2c/imx290.txt deleted file mode 100644 index a3cc21410f7c..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/imx290.txt +++ /dev/null @@ -1,57 +0,0 @@ -* Sony IMX290 1/2.8-Inch CMOS Image Sensor - -The Sony IMX290 is a 1/2.8-Inch CMOS Solid-state image sensor with -Square Pixel for Color Cameras. It is programmable through I2C and 4-wire -interfaces. The sensor output is available via CMOS logic parallel SDR output, -Low voltage LVDS DDR output and CSI-2 serial data output. The CSI-2 bus is the -default. No bindings have been defined for the other busses. - -Required Properties: -- compatible: Should be "sony,imx290" -- reg: I2C bus address of the device -- clocks: Reference to the xclk clock. -- clock-names: Should be "xclk". -- clock-frequency: Frequency of the xclk clock in Hz. -- vdddo-supply: Sensor digital IO regulator. -- vdda-supply: Sensor analog regulator. -- vddd-supply: Sensor digital core regulator. - -Optional Properties: -- reset-gpios: Sensor reset GPIO - -The imx290 device node should contain one 'port' child node with -an 'endpoint' subnode. For further reading on port node refer to -Documentation/devicetree/bindings/media/video-interfaces.txt. - -Required Properties on endpoint: -- data-lanes: check ../video-interfaces.txt -- link-frequencies: check ../video-interfaces.txt -- remote-endpoint: check ../video-interfaces.txt - -Example: - &i2c1 { - ... - imx290: camera-sensor@1a { - compatible = "sony,imx290"; - reg = <0x1a>; - - reset-gpios = <&msmgpio 35 GPIO_ACTIVE_LOW>; - pinctrl-names = "default"; - pinctrl-0 = <&camera_rear_default>; - - clocks = <&gcc GCC_CAMSS_MCLK0_CLK>; - clock-names = "xclk"; - clock-frequency = <37125000>; - - vdddo-supply = <&camera_vdddo_1v8>; - vdda-supply = <&camera_vdda_2v8>; - vddd-supply = <&camera_vddd_1v5>; - - port { - imx290_ep: endpoint { - data-lanes = <1 2 3 4>; - link-frequencies = /bits/ 64 <445500000>; - remote-endpoint = <&csiphy0_ep>; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml b/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml index 39395ea8c318..edde4201116f 100644 --- a/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml +++ b/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml @@ -104,6 +104,7 @@ additionalProperties: false examples: - | #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interfaces.h> i2c2 { #address-cells = <1>; @@ -124,7 +125,7 @@ examples: remote-endpoint = <&csi2a_ep>; link-frequencies = /bits/ 64 <199200000 210000000 499200000>; - bus-type = <4>; + bus-type = <MEDIA_BUS_TYPE_CSI2_DPHY>; }; }; }; diff --git a/Documentation/devicetree/bindings/media/i2c/ov5645.txt b/Documentation/devicetree/bindings/media/i2c/ov5645.txt deleted file mode 100644 index 72ad992f77be..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/ov5645.txt +++ /dev/null @@ -1,54 +0,0 @@ -* Omnivision 1/4-Inch 5Mp CMOS Digital Image Sensor - -The Omnivision OV5645 is a 1/4-Inch CMOS active pixel digital image sensor with -an active array size of 2592H x 1944V. It is programmable through a serial I2C -interface. - -Required Properties: -- compatible: Value should be "ovti,ov5645". -- clocks: Reference to the xclk clock. -- clock-names: Should be "xclk". -- clock-frequency: Frequency of the xclk clock. -- enable-gpios: Chip enable GPIO. Polarity is GPIO_ACTIVE_HIGH. This corresponds - to the hardware pin PWDNB which is physically active low. -- reset-gpios: Chip reset GPIO. Polarity is GPIO_ACTIVE_LOW. This corresponds to - the hardware pin RESETB. -- vdddo-supply: Chip digital IO regulator. -- vdda-supply: Chip analog regulator. -- vddd-supply: Chip digital core regulator. - -The device node must contain one 'port' child node for its digital output -video port, in accordance with the video interface bindings defined in -Documentation/devicetree/bindings/media/video-interfaces.txt. - -Example: - - &i2c1 { - ... - - ov5645: ov5645@3c { - compatible = "ovti,ov5645"; - reg = <0x3c>; - - enable-gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>; - reset-gpios = <&gpio5 20 GPIO_ACTIVE_LOW>; - pinctrl-names = "default"; - pinctrl-0 = <&camera_rear_default>; - - clocks = <&clks 200>; - clock-names = "xclk"; - clock-frequency = <24000000>; - - vdddo-supply = <&camera_dovdd_1v8>; - vdda-supply = <&camera_avdd_2v8>; - vddd-supply = <&camera_dvdd_1v2>; - - port { - ov5645_ep: endpoint { - clock-lanes = <1>; - data-lanes = <0 2>; - remote-endpoint = <&csi0_ep>; - }; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml new file mode 100644 index 000000000000..50579c947f3c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,ov4689.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Omnivision OV4689 CMOS + +maintainers: + - Mikhail Rudenko <mike.rudenko@gmail.com> + +description: | + The Omnivision OV4689 is a high performance, 1/3-inch, 4 megapixel + image sensor. Ihis chip supports high frame rate speeds up to 90 fps + at 2688x1520 resolution. It is programmable through an I2C + interface, and sensor output is sent via 1/2/4 lane MIPI CSI-2 + connection. + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: ovti,ov4689 + + reg: + maxItems: 1 + + clocks: + description: + External clock (XVCLK) for the sensor, 6-64 MHz + maxItems: 1 + + dovdd-supply: + description: + Digital I/O voltage supply, 1.7-3.0 V + + avdd-supply: + description: + Analog voltage supply, 2.6-3.0 V + + dvdd-supply: + description: + Digital core voltage supply, 1.1-1.3 V + + powerdown-gpios: + description: + GPIO connected to the powerdown pin (active low) + + reset-gpios: + maxItems: 1 + description: + GPIO connected to the reset pin (active low) + + orientation: true + + rotation: true + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + description: + Output port node, single endpoint describing the CSI-2 transmitter + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + link-frequencies: true + + required: + - data-lanes + - link-frequencies + +required: + - compatible + - reg + - clocks + - dovdd-supply + - avdd-supply + - dvdd-supply + - port + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ov4689: camera@36 { + compatible = "ovti,ov4689"; + reg = <0x36>; + + clocks = <&ov4689_clk>; + + avdd-supply = <&ov4689_avdd>; + dovdd-supply = <&ov4689_dovdd>; + dvdd-supply = <&ov4689_dvdd>; + + powerdown-gpios = <&pio 107 GPIO_ACTIVE_LOW>; + reset-gpios = <&pio 109 GPIO_ACTIVE_LOW>; + + orientation = <2>; + rotation = <0>; + + port { + wcam_out: endpoint { + remote-endpoint = <&mipi_in_wcam>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <504000000>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5645.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5645.yaml new file mode 100644 index 000000000000..52c6281a6684 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5645.yaml @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,ov5645.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OmniVision OV5645 Image Sensor Device Tree Bindings + +maintainers: + - Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> + +properties: + compatible: + const: ovti,ov5645 + + reg: + maxItems: 1 + + clocks: + description: XCLK Input Clock + + clock-frequency: + description: Frequency of the xclk clock in Hz. + + vdda-supply: + description: Analog voltage supply, 2.8 volts + + vddd-supply: + description: Digital core voltage supply, 1.5 volts + + vdddo-supply: + description: Digital I/O voltage supply, 1.8 volts + + enable-gpios: + maxItems: 1 + description: + Reference to the GPIO connected to the PWDNB pin, if any. + + reset-gpios: + maxItems: 1 + description: + Reference to the GPIO connected to the RESETB pin, if any. + + port: + description: Digital Output Port + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 2 + items: + enum: [1, 2] + + required: + - data-lanes + +required: + - compatible + - reg + - clocks + - vdddo-supply + - vdda-supply + - vddd-supply + - port + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@3c { + compatible = "ovti,ov5645"; + reg = <0x3c>; + clocks = <&clks 1>; + clock-frequency = <24000000>; + vdddo-supply = <&ov5645_vdddo_1v8>; + vdda-supply = <&ov5645_vdda_2v8>; + vddd-supply = <&ov5645_vddd_1v5>; + enable-gpios = <&gpio1 19 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ov5645>; + + port { + ov5645_ep: endpoint { + remote-endpoint = <&csi0_ep>; + data-lanes = <1 2>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov772x.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov772x.yaml index 44529425ce3a..161e6d598e1c 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov772x.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov772x.yaml @@ -105,6 +105,7 @@ additionalProperties: false examples: - | #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interfaces.h> i2c0 { #address-cells = <1>; @@ -118,7 +119,7 @@ examples: port { ov772x_0: endpoint { - bus-type = <5>; + bus-type = <MEDIA_BUS_TYPE_PARALLEL>; vsync-active = <0>; hsync-active = <0>; pclk-sample = <0>; diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov9282.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov9282.yaml index bf115ab9d926..0c4654e70d46 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov9282.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov9282.yaml @@ -16,10 +16,13 @@ description: sensor with an active array size of 1296H x 816V. It is programmable through I2C interface. The I2C client address is fixed to 0x60/0x70 as per sensor data sheet. Image data is sent through MIPI CSI-2. + OV9281 has a different lens chief ray angle. properties: compatible: - const: ovti,ov9282 + enum: + - ovti,ov9281 + - ovti,ov9282 reg: description: I2C address maxItems: 1 @@ -36,6 +39,15 @@ properties: description: Reference to the GPIO connected to the XCLR pin, if any. maxItems: 1 + avdd-supply: + description: Analog voltage supply, 2.8 volts + + dvdd-supply: + description: Digital core voltage supply, 1.2 volts + + dovdd-supply: + description: Digital I/O voltage supply, 1.8 volts + port: additionalProperties: false $ref: /schemas/graph.yaml#/$defs/port-base diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml new file mode 100644 index 000000000000..21377daae026 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx290.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX290 1/2.8-Inch CMOS Image Sensor + +maintainers: + - Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> + - Laurent Pinchart <laurent.pinchart@ideasonboard.com> + +description: |- + The Sony IMX290 is a 1/2.8-Inch CMOS Solid-state image sensor with Square + Pixel for Color Cameras. It is programmable through I2C and 4-wire + interfaces. The sensor output is available via CMOS logic parallel SDR + output, Low voltage LVDS DDR output and CSI-2 serial data output. The CSI-2 + bus is the default. No bindings have been defined for the other busses. + +properties: + compatible: + enum: + - sony,imx290 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + description: Input clock (37.125 MHz or 74.25 MHz) + items: + - const: xclk + + clock-frequency: + description: Frequency of the xclk clock in Hz + + vdda-supply: + description: Analog power supply (2.9V) + + vddd-supply: + description: Digital core power supply (1.2V) + + vdddo-supply: + description: Digital I/O power supply (1.8V) + + reset-gpios: + description: Sensor reset (XCLR) GPIO + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + description: | + Video output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + anyOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + link-frequencies: true + + required: + - data-lanes + - link-frequencies + + additionalProperties: false + +required: + - compatible + - reg + - clocks + - clock-names + - clock-frequency + - vdda-supply + - vddd-supply + - vdddo-supply + - port + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + imx290: camera-sensor@1a { + compatible = "sony,imx290"; + reg = <0x1a>; + + pinctrl-names = "default"; + pinctrl-0 = <&camera_rear_default>; + + clocks = <&gcc 90>; + clock-names = "xclk"; + clock-frequency = <37125000>; + + vdddo-supply = <&camera_vdddo_1v8>; + vdda-supply = <&camera_vdda_2v8>; + vddd-supply = <&camera_vddd_1v5>; + + reset-gpios = <&msmgpio 35 GPIO_ACTIVE_LOW>; + + port { + imx290_ep: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&csiphy0_ep>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx412.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx412.yaml index 26d1807d0bb6..60dc25ff2b9e 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx412.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx412.yaml @@ -19,7 +19,9 @@ description: properties: compatible: - const: sony,imx412 + enum: + - sony,imx412 + - sony,imx577 reg: description: I2C address maxItems: 1 diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml new file mode 100644 index 000000000000..6597e1d0e65f --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (c) 2022 STMicroelectronics SA. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings + +maintainers: + - Benjamin Mugnier <benjamin.mugnier@foss.st.com> + - Sylvain Petinot <sylvain.petinot@foss.st.com> + +description: |- + STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a + quad lanes 800Mbps per lane. + Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16. + Following part number are supported + - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors. + Maximum frame rate is 75 fps. + - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors. + Maximum frame rate is 60 fps. + +properties: + compatible: + const: st,st-vgxy61 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + VCORE-supply: + description: + Sensor digital core supply. Must be 1.2 volts. + + VDDIO-supply: + description: + Sensor digital IO supply. Must be 1.8 volts. + + VANA-supply: + description: + Sensor analog supply. Must be 2.8 volts. + + reset-gpios: + description: + Reference to the GPIO connected to the reset pin, if any. + This is an active low signal to the vgxy61. + + st,strobe-gpios-polarity: + description: + Invert polarity of illuminator's lights strobe GPIOs. + These GPIOs directly drive the illuminator LEDs. + type: boolean + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + description: + CSI lanes to use + items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + remote-endpoint: true + + required: + - data-lanes + +required: + - compatible + - clocks + - VCORE-supply + - VDDIO-supply + - VANA-supply + - port + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + vgxy61: csi2tx@10 { + compatible = "st,st-vgxy61"; + reg = <0x10>; + clocks = <&clk_ext_camera>; + VCORE-supply = <&v1v2>; + VDDIO-supply = <&v1v8>; + VANA-supply = <&v2v8>; + reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>; + port { + ep0: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&mipi_csi2_out>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml new file mode 100644 index 000000000000..b8ba85a2416c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/toshiba,tc358746.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Toshiba TC358746 Parallel to MIPI CSI2 Bridge + +maintainers: + - Marco Felsch <kernel@pengutronix.de> + +description: |- + The Toshiba TC358746 converts a parallel video stream into a MIPI CSI-2 + stream. The direction can be either parallel-in -> csi-out or csi-in -> + parallel-out The chip is programmable trough I2C and SPI but the SPI + interface is only supported in parallel-in -> csi-out mode. + + Note that the current device tree bindings only support the + parallel-in -> csi-out path. + +properties: + compatible: + const: toshiba,tc358746 + + reg: + maxItems: 1 + + clocks: + description: + The phandle to the reference clock source. This corresponds to the + hardware pin REFCLK. + maxItems: 1 + + clock-names: + const: refclk + + "#clock-cells": + description: | + The bridge can act as clock provider for the sensor. To enable this + support #clock-cells must be specified. Attention if this feature is used + then the mclk rate must be at least: (2 * link-frequency) / 8 + `------------------´ ^ + internal PLL rate smallest possible + mclk-div + const: 0 + + clock-output-names: + description: + The clock name of the MCLK output, the default name is tc358746-mclk. + maxItems: 1 + + vddc-supply: + description: Digital core voltage supply, 1.2 volts + + vddio-supply: + description: Digital I/O voltage supply, 1.8 volts + + vddmipi-supply: + description: MIPI CSI phy voltage supply, 1.2 volts + + reset-gpios: + description: + The phandle and specifier for the GPIO that controls the chip reset. + This corresponds to the hardware pin RESX which is physically active low. + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: Input port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + hsync-active: true + vsync-active: true + bus-type: + enum: [ 5, 6 ] + + required: + - hsync-active + - vsync-active + - bus-type + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + description: Output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + clock-noncontinuous: true + link-frequencies: true + + required: + - data-lanes + - link-frequencies + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - clock-names + - vddc-supply + - vddio-supply + - vddmipi-supply + - ports + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + csi-bridge@e { + compatible = "toshiba,tc358746"; + reg = <0xe>; + + clocks = <&refclk>; + clock-names = "refclk"; + + reset-gpios = <&gpio 2 GPIO_ACTIVE_LOW>; + + vddc-supply = <&v1_2d>; + vddio-supply = <&v1_8d>; + vddmipi-supply = <&v1_2d>; + + /* sensor mclk provider */ + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* Input */ + port@0 { + reg = <0>; + tc358746_in: endpoint { + remote-endpoint = <&sensor_out>; + hsync-active = <0>; + vsync-active = <0>; + bus-type = <5>; + }; + }; + + /* Output */ + port@1 { + reg = <1>; + tc358746_out: endpoint { + remote-endpoint = <&mipi_csi2_in>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <216000000>; + }; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/marvell,mmp2-ccic.yaml b/Documentation/devicetree/bindings/media/marvell,mmp2-ccic.yaml index b39b84c5f012..0e3478551e13 100644 --- a/Documentation/devicetree/bindings/media/marvell,mmp2-ccic.yaml +++ b/Documentation/devicetree/bindings/media/marvell,mmp2-ccic.yaml @@ -68,6 +68,7 @@ additionalProperties: false examples: - | #include <dt-bindings/clock/marvell,mmp2.h> + #include <dt-bindings/media/video-interfaces.h> #include <dt-bindings/power/marvell,mmp2.h> camera@d420a000 { @@ -83,7 +84,7 @@ examples: port { camera0_0: endpoint { remote-endpoint = <&ov7670_0>; - bus-type = <5>; /* Parallel */ + bus-type = <MEDIA_BUS_TYPE_PARALLEL>; hsync-active = <1>; /* Active high */ vsync-active = <1>; /* Active high */ pclk-sample = <0>; /* Falling */ diff --git a/Documentation/devicetree/bindings/media/mediatek,mt8195-jpegdec.yaml b/Documentation/devicetree/bindings/media/mediatek,mt8195-jpegdec.yaml new file mode 100644 index 000000000000..71595c013dbb --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt8195-jpegdec.yaml @@ -0,0 +1,168 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/mediatek,mt8195-jpegdec.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek JPEG Decoder + +maintainers: + - kyrie wu <kyrie.wu@mediatek.corp-partner.google.com> + +description: + MediaTek JPEG Decoder is the JPEG decode hardware present in MediaTek SoCs + +properties: + compatible: + const: mediatek,mt8195-jpgdec + + power-domains: + maxItems: 1 + + iommus: + maxItems: 6 + description: + Points to the respective IOMMU block with master port as argument, see + Documentation/devicetree/bindings/iommu/mediatek,iommu.yaml for details. + Ports are according to the HW. + + dma-ranges: + maxItems: 1 + description: | + Describes the physical address space of IOMMU maps to memory. + + "#address-cells": + const: 2 + + "#size-cells": + const: 2 + + ranges: true + +# Required child node: +patternProperties: + "^jpgdec@[0-9a-f]+$": + type: object + description: + The jpeg decoder hardware device node which should be added as subnodes to + the main jpeg node. + + properties: + compatible: + const: mediatek,mt8195-jpgdec-hw + + reg: + maxItems: 1 + + iommus: + minItems: 1 + maxItems: 32 + description: + List of the hardware port in respective IOMMU block for current Socs. + Refer to bindings/iommu/mediatek,iommu.yaml. + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: jpgdec + + power-domains: + maxItems: 1 + + required: + - compatible + - reg + - iommus + - interrupts + - clocks + - clock-names + - power-domains + + additionalProperties: false + +required: + - compatible + - power-domains + - iommus + - dma-ranges + - ranges + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/memory/mt8195-memory-port.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/clock/mt8195-clk.h> + #include <dt-bindings/power/mt8195-power.h> + + soc { + #address-cells = <2>; + #size-cells = <2>; + + jpgdec-master { + compatible = "mediatek,mt8195-jpgdec"; + power-domains = <&spm MT8195_POWER_DOMAIN_VDEC1>; + iommus = <&iommu_vpp M4U_PORT_L19_JPGDEC_WDMA0>, + <&iommu_vpp M4U_PORT_L19_JPGDEC_BSDMA0>, + <&iommu_vpp M4U_PORT_L19_JPGDEC_WDMA1>, + <&iommu_vpp M4U_PORT_L19_JPGDEC_BSDMA1>, + <&iommu_vpp M4U_PORT_L19_JPGDEC_BUFF_OFFSET1>, + <&iommu_vpp M4U_PORT_L19_JPGDEC_BUFF_OFFSET0>; + dma-ranges = <0x1 0x0 0x0 0x40000000 0x0 0xfff00000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + jpgdec@1a040000 { + compatible = "mediatek,mt8195-jpgdec-hw"; + reg = <0 0x1a040000 0 0x10000>;/* JPGDEC_C0 */ + iommus = <&iommu_vdo M4U_PORT_L19_JPGDEC_WDMA0>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BSDMA0>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_WDMA1>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BSDMA1>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BUFF_OFFSET1>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BUFF_OFFSET0>; + interrupts = <GIC_SPI 343 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&vencsys CLK_VENC_JPGDEC>; + clock-names = "jpgdec"; + power-domains = <&spm MT8195_POWER_DOMAIN_VDEC0>; + }; + + jpgdec@1a050000 { + compatible = "mediatek,mt8195-jpgdec-hw"; + reg = <0 0x1a050000 0 0x10000>;/* JPGDEC_C1 */ + iommus = <&iommu_vdo M4U_PORT_L19_JPGDEC_WDMA0>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BSDMA0>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_WDMA1>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BSDMA1>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BUFF_OFFSET1>, + <&iommu_vdo M4U_PORT_L19_JPGDEC_BUFF_OFFSET0>; + interrupts = <GIC_SPI 344 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&vencsys CLK_VENC_JPGDEC_C1>; + clock-names = "jpgdec"; + power-domains = <&spm MT8195_POWER_DOMAIN_VDEC1>; + }; + + jpgdec@1b040000 { + compatible = "mediatek,mt8195-jpgdec-hw"; + reg = <0 0x1b040000 0 0x10000>;/* JPGDEC_C2 */ + iommus = <&iommu_vpp M4U_PORT_L20_JPGDEC_WDMA0>, + <&iommu_vpp M4U_PORT_L20_JPGDEC_BSDMA0>, + <&iommu_vpp M4U_PORT_L20_JPGDEC_WDMA1>, + <&iommu_vpp M4U_PORT_L20_JPGDEC_BSDMA1>, + <&iommu_vpp M4U_PORT_L20_JPGDEC_BUFF_OFFSET1>, + <&iommu_vpp M4U_PORT_L20_JPGDEC_BUFF_OFFSET0>; + interrupts = <GIC_SPI 348 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&vencsys_core1 CLK_VENC_CORE1_JPGDEC>; + clock-names = "jpgdec"; + power-domains = <&spm MT8195_POWER_DOMAIN_VDEC2>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/mediatek,mt8195-jpegenc.yaml b/Documentation/devicetree/bindings/media/mediatek,mt8195-jpegenc.yaml new file mode 100644 index 000000000000..95990539f7c0 --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt8195-jpegenc.yaml @@ -0,0 +1,147 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/mediatek,mt8195-jpegenc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek JPEG Encoder + +maintainers: + - kyrie wu <kyrie.wu@mediatek.corp-partner.google.com> + +description: + MediaTek JPEG Encoder is the JPEG encode hardware present in MediaTek SoCs + +properties: + compatible: + const: mediatek,mt8195-jpgenc + + power-domains: + maxItems: 1 + + iommus: + maxItems: 4 + description: + Points to the respective IOMMU block with master port as argument, see + Documentation/devicetree/bindings/iommu/mediatek,iommu.yaml for details. + Ports are according to the HW. + + dma-ranges: + maxItems: 1 + description: | + Describes the physical address space of IOMMU maps to memory. + + "#address-cells": + const: 2 + + "#size-cells": + const: 2 + + ranges: true + +# Required child node: +patternProperties: + "^jpgenc@[0-9a-f]+$": + type: object + description: + The jpeg encoder hardware device node which should be added as subnodes to + the main jpeg node. + + properties: + compatible: + const: mediatek,mt8195-jpgenc-hw + + reg: + maxItems: 1 + + iommus: + minItems: 1 + maxItems: 32 + description: + List of the hardware port in respective IOMMU block for current Socs. + Refer to bindings/iommu/mediatek,iommu.yaml. + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: jpgenc + + power-domains: + maxItems: 1 + + required: + - compatible + - reg + - iommus + - interrupts + - clocks + - clock-names + - power-domains + + additionalProperties: false + +required: + - compatible + - power-domains + - iommus + - dma-ranges + - ranges + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/memory/mt8195-memory-port.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/clock/mt8195-clk.h> + #include <dt-bindings/power/mt8195-power.h> + + soc { + #address-cells = <2>; + #size-cells = <2>; + + jpgenc-master { + compatible = "mediatek,mt8195-jpgenc"; + power-domains = <&spm MT8195_POWER_DOMAIN_VENC_CORE1>; + iommus = <&iommu_vpp M4U_PORT_L20_JPGENC_Y_RDMA>, + <&iommu_vpp M4U_PORT_L20_JPGENC_C_RDMA>, + <&iommu_vpp M4U_PORT_L20_JPGENC_Q_TABLE>, + <&iommu_vpp M4U_PORT_L20_JPGENC_BSDMA>; + dma-ranges = <0x1 0x0 0x0 0x40000000 0x0 0xfff00000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + jpgenc@1a030000 { + compatible = "mediatek,mt8195-jpgenc-hw"; + reg = <0 0x1a030000 0 0x10000>; + iommus = <&iommu_vdo M4U_PORT_L19_JPGENC_Y_RDMA>, + <&iommu_vdo M4U_PORT_L19_JPGENC_C_RDMA>, + <&iommu_vdo M4U_PORT_L19_JPGENC_Q_TABLE>, + <&iommu_vdo M4U_PORT_L19_JPGENC_BSDMA>; + interrupts = <GIC_SPI 342 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&vencsys CLK_VENC_JPGENC>; + clock-names = "jpgenc"; + power-domains = <&spm MT8195_POWER_DOMAIN_VENC>; + }; + + jpgenc@1b030000 { + compatible = "mediatek,mt8195-jpgenc-hw"; + reg = <0 0x1b030000 0 0x10000>; + iommus = <&iommu_vpp M4U_PORT_L20_JPGENC_Y_RDMA>, + <&iommu_vpp M4U_PORT_L20_JPGENC_C_RDMA>, + <&iommu_vpp M4U_PORT_L20_JPGENC_Q_TABLE>, + <&iommu_vpp M4U_PORT_L20_JPGENC_BSDMA>; + interrupts = <GIC_SPI 347 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&vencsys_core1 CLK_VENC_CORE1_JPGENC>; + clock-names = "jpgenc"; + power-domains = <&spm MT8195_POWER_DOMAIN_VENC_CORE1>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/mediatek,vcodec-encoder.yaml b/Documentation/devicetree/bindings/media/mediatek,vcodec-encoder.yaml index 32aee09aea33..0f2ea8d9a10c 100644 --- a/Documentation/devicetree/bindings/media/mediatek,vcodec-encoder.yaml +++ b/Documentation/devicetree/bindings/media/mediatek,vcodec-encoder.yaml @@ -67,6 +67,12 @@ properties: power-domains: maxItems: 1 + "#address-cells": + const: 2 + + "#size-cells": + const: 2 + required: - compatible - reg @@ -84,7 +90,9 @@ allOf: contains: enum: - mediatek,mt8183-vcodec-enc + - mediatek,mt8188-vcodec-enc - mediatek,mt8192-vcodec-enc + - mediatek,mt8195-vcodec-enc then: required: @@ -107,7 +115,9 @@ allOf: compatible: enum: - mediatek,mt8173-vcodec-enc + - mediatek,mt8188-vcodec-enc - mediatek,mt8192-vcodec-enc + - mediatek,mt8195-vcodec-enc then: properties: @@ -118,7 +128,7 @@ allOf: clock-names: items: - const: venc_sel - else: # for vp8 hw decoder + else: # for vp8 hw encoder properties: clock: items: diff --git a/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml b/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml index 5e8d001492cc..cfabf360f278 100644 --- a/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml +++ b/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml @@ -22,6 +22,7 @@ properties: - items: - enum: - mediatek,mt7623-jpgdec + - mediatek,mt8188-jpgdec - const: mediatek,mt2701-jpgdec reg: diff --git a/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml b/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml index fc727300b493..c8412e8ab353 100644 --- a/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml +++ b/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml @@ -19,6 +19,7 @@ properties: - mediatek,mt2701-jpgenc - mediatek,mt8183-jpgenc - mediatek,mt8186-jpgenc + - mediatek,mt8188-jpgenc - const: mediatek,mtk-jpgenc reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/media/microchip,xisc.yaml b/Documentation/devicetree/bindings/media/microchip,xisc.yaml index 8b37fccab5e2..25f5f79d40ce 100644 --- a/Documentation/devicetree/bindings/media/microchip,xisc.yaml +++ b/Documentation/devicetree/bindings/media/microchip,xisc.yaml @@ -106,6 +106,7 @@ examples: #include <dt-bindings/interrupt-controller/arm-gic.h> #include <dt-bindings/clock/at91.h> #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/media/video-interfaces.h> xisc: xisc@e1408000 { compatible = "microchip,sama7g5-isc"; @@ -118,7 +119,7 @@ examples: port { xisc_in: endpoint { - bus-type = <5>; /* Parallel */ + bus-type = <MEDIA_BUS_TYPE_PARALLEL>; remote-endpoint = <&csi2dc_out>; hsync-active = <1>; vsync-active = <1>; diff --git a/Documentation/devicetree/bindings/media/renesas,rzg2l-cru.yaml b/Documentation/devicetree/bindings/media/renesas,rzg2l-cru.yaml new file mode 100644 index 000000000000..7dde7967c886 --- /dev/null +++ b/Documentation/devicetree/bindings/media/renesas,rzg2l-cru.yaml @@ -0,0 +1,157 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (C) 2022 Renesas Electronics Corp. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/renesas,rzg2l-cru.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/G2L (and alike SoC's) Camera Data Receiving Unit (CRU) Image processing + +maintainers: + - Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> + +description: + The CRU image processing module is a data conversion module equipped with pixel + color space conversion, LUT, pixel format conversion, etc. An MIPI CSI-2 input and + parallel (including ITU-R BT.656) input are provided as the image sensor interface. + +properties: + compatible: + items: + - enum: + - renesas,r9a07g044-cru # RZ/G2{L,LC} + - renesas,r9a07g054-cru # RZ/V2L + - const: renesas,rzg2l-cru + + reg: + maxItems: 1 + + interrupts: + maxItems: 3 + + interrupt-names: + items: + - const: image_conv + - const: image_conv_err + - const: axi_mst_err + + clocks: + items: + - description: CRU Main clock + - description: CRU Register access clock + - description: CRU image transfer clock + + clock-names: + items: + - const: video + - const: apb + - const: axi + + power-domains: + maxItems: 1 + + resets: + items: + - description: CRU_PRESETN reset terminal + - description: CRU_ARESETN reset terminal + + reset-names: + items: + - const: presetn + - const: aresetn + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: + Input port node, single endpoint describing a parallel input source. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + hsync-active: true + vsync-active: true + bus-width: true + data-shift: true + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + Input port node, describing the Image Processing module connected to the + CSI-2 receiver. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - resets + - reset-names + - power-domains + +additionalProperties: false + +examples: + # Device node example with CSI-2 + - | + #include <dt-bindings/clock/r9a07g044-cpg.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + + cru: video@10830000 { + compatible = "renesas,r9a07g044-cru", "renesas,rzg2l-cru"; + reg = <0x10830000 0x400>; + interrupts = <GIC_SPI 167 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 168 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 169 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "image_conv", "image_conv_err", "axi_mst_err"; + clocks = <&cpg CPG_MOD R9A07G044_CRU_VCLK>, + <&cpg CPG_MOD R9A07G044_CRU_PCLK>, + <&cpg CPG_MOD R9A07G044_CRU_ACLK>; + clock-names = "video", "apb", "axi"; + power-domains = <&cpg>; + resets = <&cpg R9A07G044_CRU_PRESETN>, + <&cpg R9A07G044_CRU_ARESETN>; + reset-names = "presetn", "aresetn"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + cru_parallel_in: endpoint@0 { + reg = <0>; + remote-endpoint= <&ov5642>; + hsync-active = <1>; + vsync-active = <1>; + }; + }; + + port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + cru_csi_in: endpoint@0 { + reg = <0>; + remote-endpoint= <&csi_cru_in>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/renesas,rzg2l-csi2.yaml b/Documentation/devicetree/bindings/media/renesas,rzg2l-csi2.yaml new file mode 100644 index 000000000000..67eea2ac1d22 --- /dev/null +++ b/Documentation/devicetree/bindings/media/renesas,rzg2l-csi2.yaml @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (C) 2022 Renesas Electronics Corp. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/renesas,rzg2l-csi2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/G2L (and alike SoC's) MIPI CSI-2 receiver + +maintainers: + - Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> + +description: + The CSI-2 receiver device provides MIPI CSI-2 capabilities for the Renesas RZ/G2L + (and alike SoCs). MIPI CSI-2 is part of the CRU block which is used in conjunction + with the Image Processing module, which provides the video capture capabilities. + +properties: + compatible: + items: + - enum: + - renesas,r9a07g044-csi2 # RZ/G2{L,LC} + - renesas,r9a07g054-csi2 # RZ/V2L + - const: renesas,rzg2l-csi2 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Internal clock for connecting CRU and MIPI + - description: CRU Main clock + - description: CRU Register access clock + + clock-names: + items: + - const: system + - const: video + - const: apb + + power-domains: + maxItems: 1 + + resets: + items: + - description: CRU_PRESETN reset terminal + - description: CRU_CMN_RSTB reset terminal + + reset-names: + items: + - const: presetn + - const: cmn-rstb + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: + Input port node, single endpoint describing the CSI-2 transmitter. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + items: + maximum: 4 + + required: + - clock-lanes + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + Output port node, Image Processing block connected to the CSI-2 receiver. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - power-domains + - resets + - reset-names + - ports + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/r9a07g044-cpg.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + + csi: csi@10830400 { + compatible = "renesas,r9a07g044-csi2", "renesas,rzg2l-csi2"; + reg = <0x10830400 0xfc00>; + interrupts = <GIC_SPI 166 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&cpg CPG_MOD R9A07G044_CRU_SYSCLK>, + <&cpg CPG_MOD R9A07G044_CRU_VCLK>, + <&cpg CPG_MOD R9A07G044_CRU_PCLK>; + clock-names = "system", "video", "apb"; + power-domains = <&cpg>; + resets = <&cpg R9A07G044_CRU_PRESETN>, + <&cpg R9A07G044_CRU_CMN_RSTB>; + reset-names = "presetn", "cmn-rstb"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + csi2_in: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + remote-endpoint = <&ov5645_ep>; + }; + }; + + port@1 { + #address-cells = <1>; + #size-cells = <0>; + + reg = <1>; + + csi2cru: endpoint@0 { + reg = <0>; + remote-endpoint = <&crucsi2>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/s5p-mfc.txt b/Documentation/devicetree/bindings/media/s5p-mfc.txt index aa54c8159d9f..8eb90c043d5d 100644 --- a/Documentation/devicetree/bindings/media/s5p-mfc.txt +++ b/Documentation/devicetree/bindings/media/s5p-mfc.txt @@ -10,10 +10,12 @@ Required properties: - compatible : value should be either one among the following (a) "samsung,mfc-v5" for MFC v5 present in Exynos4 SoCs (b) "samsung,mfc-v6" for MFC v6 present in Exynos5 SoCs - (c) "samsung,mfc-v7" for MFC v7 present in Exynos5420 SoC - (d) "samsung,mfc-v8" for MFC v8 present in Exynos5800 SoC - (e) "samsung,exynos5433-mfc" for MFC v8 present in Exynos5433 SoC - (f) "samsung,mfc-v10" for MFC v10 present in Exynos7880 SoC + (c) "samsung,exynos3250-mfc", "samsung,mfc-v7" for MFC v7 + present in Exynos3250 SoC + (d) "samsung,mfc-v7" for MFC v7 present in Exynos5420 SoC + (e) "samsung,mfc-v8" for MFC v8 present in Exynos5800 SoC + (f) "samsung,exynos5433-mfc" for MFC v8 present in Exynos5433 SoC + (g) "samsung,mfc-v10" for MFC v10 present in Exynos7880 SoC - reg : Physical base address of the IP registers and length of memory mapped region. diff --git a/Documentation/devicetree/bindings/media/samsung-s5c73m3.txt b/Documentation/devicetree/bindings/media/samsung-s5c73m3.txt index 21f31fdf5543..f0ea9adad442 100644 --- a/Documentation/devicetree/bindings/media/samsung-s5c73m3.txt +++ b/Documentation/devicetree/bindings/media/samsung-s5c73m3.txt @@ -76,7 +76,7 @@ i2c@138a000000 { clock-frequency = <24000000>; clocks = <&clk 0>; clock-names = "cis_extclk"; - reset-gpios = <&gpf1 3 1>; + xshutdown-gpios = <&gpf1 3 1>; standby-gpios = <&gpm0 1 1>; port { s5c73m3_ep: endpoint { diff --git a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml index 9c1262a276b5..e80fcdf280f0 100644 --- a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml +++ b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml @@ -90,7 +90,9 @@ examples: - | #include <dt-bindings/interrupt-controller/arm-gic.h> #include <dt-bindings/clock/stm32mp1-clks.h> + #include <dt-bindings/media/video-interfaces.h> #include <dt-bindings/reset/stm32mp1-resets.h> + dcmi: dcmi@4c006000 { compatible = "st,stm32-dcmi"; reg = <0x4c006000 0x400>; @@ -104,7 +106,7 @@ examples: port { dcmi_0: endpoint { remote-endpoint = <&ov5640_0>; - bus-type = <5>; + bus-type = <MEDIA_BUS_TYPE_PARALLEL>; bus-width = <8>; hsync-active = <0>; vsync-active = <0>; diff --git a/Documentation/devicetree/bindings/media/video-interfaces.yaml b/Documentation/devicetree/bindings/media/video-interfaces.yaml index 68c3b9871cf3..34bdad028180 100644 --- a/Documentation/devicetree/bindings/media/video-interfaces.yaml +++ b/Documentation/devicetree/bindings/media/video-interfaces.yaml @@ -145,9 +145,10 @@ properties: pclk-sample: $ref: /schemas/types.yaml#/definitions/uint32 - enum: [ 0, 1 ] + enum: [ 0, 1, 2 ] description: - Sample data on rising (1) or falling (0) edge of the pixel clock signal. + Sample data on falling (0), rising (1) or both (2) edges of the pixel + clock signal. sync-on-green-active: $ref: /schemas/types.yaml#/definitions/uint32 diff --git a/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst b/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst index b0efce40471f..411d42a742f3 100644 --- a/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst +++ b/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later +.. _cec_pin_error_inj: + CEC Pin Framework Error Injection ================================= diff --git a/Documentation/userspace-api/media/drivers/aspeed-video.rst b/Documentation/userspace-api/media/drivers/aspeed-video.rst new file mode 100644 index 000000000000..1b0cb1e3eba8 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/aspeed-video.rst @@ -0,0 +1,65 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: <isonum.txt> + +ASPEED video driver +=================== + +ASPEED Video Engine found on AST2400/2500/2600 SoC supports high performance +video compressions with a wide range of video quality and compression ratio +options. The adopted compressing algorithm is a modified JPEG algorithm. + +There are 2 types of compressions in this IP. + +* JPEG JFIF standard mode: for single frame and management compression +* ASPEED proprietary mode: for multi-frame and differential compression. + Support 2-pass (high quality) video compression scheme (Patent pending by + ASPEED). Provide visually lossless video compression quality or to reduce + the network average loading under intranet KVM applications. + +VIDIOC_S_FMT can be used to choose which format you want. V4L2_PIX_FMT_JPEG +stands for JPEG JFIF standard mode; V4L2_PIX_FMT_AJPG stands for ASPEED +proprietary mode. + +More details on the ASPEED video hardware operations can be found in +*chapter 6.2.16 KVM Video Driver* of SDK_User_Guide which available on +AspeedTech-BMC/openbmc/releases. + +The ASPEED video driver implements the following driver-specific control: + +``V4L2_CID_ASPEED_HQ_MODE`` +--------------------------- + Enable/Disable ASPEED's High quality mode. This is a private control + that can be used to enable high quality for aspeed proprietary mode. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - ``(0)`` + - ASPEED HQ mode is disabled. + * - ``(1)`` + - ASPEED HQ mode is enabled. + +``V4L2_CID_ASPEED_HQ_JPEG_QUALITY`` +----------------------------------- + Define the quality of ASPEED's High quality mode. This is a private control + that can be used to decide compression quality if High quality mode enabled + . Higher the value, better the quality and bigger the size. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - ``(1)`` + - minimum + * - ``(12)`` + - maximum + * - ``(1)`` + - step + * - ``(1)`` + - default + +**Copyright** |copy| 2022 ASPEED Technology Inc. diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst index 32f82aed47d9..915dbf0f4db5 100644 --- a/Documentation/userspace-api/media/drivers/index.rst +++ b/Documentation/userspace-api/media/drivers/index.rst @@ -31,6 +31,7 @@ For more details see the file COPYING in the source distribution of Linux. :maxdepth: 5 :numbered: + aspeed-video ccs cx2341x-uapi dw100 @@ -38,4 +39,5 @@ For more details see the file COPYING in the source distribution of Linux. max2175 meye-uapi omap3isp-uapi + st-vgxy61 uvcvideo diff --git a/Documentation/userspace-api/media/drivers/st-vgxy61.rst b/Documentation/userspace-api/media/drivers/st-vgxy61.rst new file mode 100644 index 000000000000..d9e3b80e3a96 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/st-vgxy61.rst @@ -0,0 +1,25 @@ +.. SPDX-License-Identifier: GPL-2.0 + +ST VGXY61 camera sensor driver +============================== + +The ST VGXY61 driver implements the following controls: + +``V4L2_CID_HDR_SENSOR_MODE`` +------------------------------- + Change the sensor HDR mode. A HDR picture is obtained by merging two + captures of the same scene using two different exposure periods. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - HDR linearize + - The merger outputs a long exposure capture as long as it is not + saturated. + * - HDR substraction + - This involves subtracting the short exposure frame from the long + exposure frame. + * - No HDR + - This mode is used for standard dynamic range (SDR) exposures. diff --git a/Documentation/userspace-api/media/dvb/fe_property_parameters.rst b/Documentation/userspace-api/media/dvb/fe_property_parameters.rst index ecd84a8790a2..1717a0565fe8 100644 --- a/Documentation/userspace-api/media/dvb/fe_property_parameters.rst +++ b/Documentation/userspace-api/media/dvb/fe_property_parameters.rst @@ -89,16 +89,21 @@ ATSC (version 1) 8-VSB and 16-VSB. DMTB 4-QAM, 16-QAM, 32-QAM, 64-QAM and 4-QAM-NR. DVB-C Annex A/C 16-QAM, 32-QAM, 64-QAM and 256-QAM. DVB-C Annex B 64-QAM. +DVB-C2 QPSK, 16-QAM, 64-QAM, 256-QAM, 1024-QAM and 4096-QAM. DVB-T QPSK, 16-QAM and 64-QAM. DVB-T2 QPSK, 16-QAM, 64-QAM and 256-QAM. DVB-S No need to set. It supports only QPSK. DVB-S2 QPSK, 8-PSK, 16-APSK and 32-APSK. +DVB-S2X 8-APSK-L, 16-APSK-L, 32-APSK-L, 64-APSK and 64-APSK-L. ISDB-T QPSK, DQPSK, 16-QAM and 64-QAM. ISDB-S 8-PSK, QPSK and BPSK. ======================= ======================================================= .. note:: + As DVB-S2X specifies extensions to the DVB-S2 standard, the same + delivery system enum value is used (SYS_DVBS2). + Please notice that some of the above modulation types may not be defined currently at the Kernel. The reason is simple: no driver needed such definition yet. @@ -854,9 +859,10 @@ The acceptable values are defined by :c:type:`fe_guard_interval`. #. If ``DTV_GUARD_INTERVAL`` is set the ``GUARD_INTERVAL_AUTO`` the hardware will try to find the correct guard interval (if capable) and will use TMCC to fill in the missing parameters. - #. Intervals ``GUARD_INTERVAL_1_128``, ``GUARD_INTERVAL_19_128`` - and ``GUARD_INTERVAL_19_256`` are used only for DVB-T2 at - present. + #. Interval ``GUARD_INTERVAL_1_64`` is used only for DVB-C2. + #. Interval ``GUARD_INTERVAL_1_128`` is used for both DVB-C2 and DVB_T2. + #. Intervals ``GUARD_INTERVAL_19_128`` and ``GUARD_INTERVAL_19_256`` are + used only for DVB-T2. #. Intervals ``GUARD_INTERVAL_PN420``, ``GUARD_INTERVAL_PN595`` and ``GUARD_INTERVAL_PN945`` are used only for DMTB at the present. On such standard, only those intervals and ``GUARD_INTERVAL_AUTO`` @@ -916,14 +922,15 @@ The acceptable values are defined by :c:type:`fe_hierarchy`. DTV_STREAM_ID ============= -Used on DVB-S2, DVB-T2 and ISDB-S. +Used on DVB-C2, DVB-S2, DVB-T2 and ISDB-S. -DVB-S2, DVB-T2 and ISDB-S support the transmission of several streams on -a single transport stream. This property enables the digital TV driver to -handle substream filtering, when supported by the hardware. By default, -substream filtering is disabled. +DVB-C2, DVB-S2, DVB-T2 and ISDB-S support the transmission of several +streams on a single transport stream. This property enables the digital +TV driver to handle substream filtering, when supported by the hardware. +By default, substream filtering is disabled. -For DVB-S2 and DVB-T2, the valid substream id range is from 0 to 255. +For DVB-C2, DVB-S2 and DVB-T2, the valid substream id range is from 0 to +255. For ISDB, the valid substream id range is from 1 to 65535. diff --git a/Documentation/userspace-api/media/frontend.h.rst.exceptions b/Documentation/userspace-api/media/frontend.h.rst.exceptions index 6283702c08c8..8b73fee11a79 100644 --- a/Documentation/userspace-api/media/frontend.h.rst.exceptions +++ b/Documentation/userspace-api/media/frontend.h.rst.exceptions @@ -86,6 +86,13 @@ ignore symbol APSK_16 ignore symbol APSK_32 ignore symbol DQPSK ignore symbol QAM_4_NR +ignore symbol QAM_1024 +ignore symbol QAM_4096 +ignore symbol APSK_8_L +ignore symbol APSK_16_L +ignore symbol APSK_32_L +ignore symbol APSK_64 +ignore symbol APSK_64_L ignore symbol SEC_VOLTAGE_13 ignore symbol SEC_VOLTAGE_18 @@ -119,6 +126,22 @@ ignore symbol FEC_AUTO ignore symbol FEC_3_5 ignore symbol FEC_9_10 ignore symbol FEC_2_5 +ignore symbol FEC_1_3 +ignore symbol FEC_1_4 +ignore symbol FEC_5_9 +ignore symbol FEC_7_9 +ignore symbol FEC_8_15 +ignore symbol FEC_11_15 +ignore symbol FEC_13_18 +ignore symbol FEC_9_20 +ignore symbol FEC_11_20 +ignore symbol FEC_23_36 +ignore symbol FEC_25_36 +ignore symbol FEC_13_45 +ignore symbol FEC_26_45 +ignore symbol FEC_28_45 +ignore symbol FEC_32_45 +ignore symbol FEC_77_90 ignore symbol TRANSMISSION_MODE_AUTO ignore symbol TRANSMISSION_MODE_1K @@ -143,6 +166,7 @@ ignore symbol GUARD_INTERVAL_19_256 ignore symbol GUARD_INTERVAL_PN420 ignore symbol GUARD_INTERVAL_PN595 ignore symbol GUARD_INTERVAL_PN945 +ignore symbol GUARD_INTERVAL_1_64 ignore symbol HIERARCHY_NONE ignore symbol HIERARCHY_AUTO @@ -163,6 +187,9 @@ ignore symbol ROLLOFF_35 ignore symbol ROLLOFF_20 ignore symbol ROLLOFF_25 ignore symbol ROLLOFF_AUTO +ignore symbol ROLLOFF_15 +ignore symbol ROLLOFF_10 +ignore symbol ROLLOFF_5 ignore symbol INVERSION_ON ignore symbol INVERSION_OFF @@ -187,6 +214,7 @@ ignore symbol SYS_DAB ignore symbol SYS_DSS ignore symbol SYS_CMMB ignore symbol SYS_DVBH +ignore symbol SYS_DVBC2 ignore symbol ATSCMH_SCCC_BLK_SEP ignore symbol ATSCMH_SCCC_BLK_COMB diff --git a/Documentation/userspace-api/media/v4l/buffer.rst b/Documentation/userspace-api/media/v4l/buffer.rst index 4638ec64db00..04dec3e570ed 100644 --- a/Documentation/userspace-api/media/v4l/buffer.rst +++ b/Documentation/userspace-api/media/v4l/buffer.rst @@ -187,10 +187,8 @@ struct v4l2_buffer on the negotiated data format and may change with each buffer for compressed variable size data like JPEG images. Drivers must set this field when ``type`` refers to a capture stream, applications - when it refers to an output stream. If the application sets this - to 0 for an output stream, then ``bytesused`` will be set to the - size of the buffer (see the ``length`` field of this struct) by - the driver. For multiplanar formats this field is ignored and the + when it refers to an output stream. For multiplanar formats this field + is ignored and the ``planes`` pointer is used instead. * - __u32 - ``flags`` @@ -327,10 +325,7 @@ struct v4l2_plane - ``bytesused`` - The number of bytes occupied by data in the plane (its payload). Drivers must set this field when ``type`` refers to a capture - stream, applications when it refers to an output stream. If the - application sets this to 0 for an output stream, then - ``bytesused`` will be set to the size of the plane (see the - ``length`` field of this struct) by the driver. + stream, applications when it refers to an output stream. .. note:: diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst index 4c5061aa9cd4..daa4f40869f8 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst @@ -661,3 +661,11 @@ enum v4l2_scene_mode - .. [#f1] This control may be changed to a menu control in the future, if more options are required. + +``V4L2_CID_HDR_SENSOR_MODE (menu)`` + Change the sensor HDR mode. A HDR picture is obtained by merging two + captures of the same scene using two different exposure periods. HDR mode + describes the way these two captures are merged in the sensor. + + As modes differ for each sensor, menu items are not standardized by this + control and are left to the programmer. diff --git a/Documentation/userspace-api/media/v4l/pixfmt-reserved.rst b/Documentation/userspace-api/media/v4l/pixfmt-reserved.rst index 0ff68cd8cf62..73cd99828010 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-reserved.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-reserved.rst @@ -258,6 +258,23 @@ please make a proposal on the linux-media mailing list. and it is used by various multimedia hardware blocks like GPU, display controllers, ISP and video accelerators. It contains four planes for progressive video. + * .. _V4L2-PIX-FMT-AJPG: + + - ``V4L2_PIX_FMT_AJPG`` + - 'AJPG' + - ASPEED JPEG format used by the aspeed-video driver on Aspeed platforms, + which is generally adapted for remote KVM. + On each frame compression, I will compare the new frame with previous + one to decide which macroblock's data is changed, and only the changed + macroblocks will be compressed. + + The implementation is based on AST2600 A3 datasheet, revision 0.9, which + is not publicly available. Or you can reference Video stream data format + – ASPEED mode compression of SDK_User_Guide which available on + AspeedTech-BMC/openbmc/releases. + + Decoder's implementation can be found here, + `aspeed_codec <https://github.com/AspeedTech-BMC/aspeed_codec/>`__ .. raw:: latex \normalsize diff --git a/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst b/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst index 10b1feeb0b57..f1d5bb7b806d 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst @@ -273,7 +273,9 @@ of the luma plane. .. _V4L2-PIX-FMT-NV12-16L16: .. _V4L2-PIX-FMT-NV12-32L32: .. _V4L2-PIX-FMT-NV12M-8L128: +.. _V4L2-PIX-FMT-NV12-8L128: .. _V4L2-PIX-FMT-NV12M-10BE-8L128: +.. _V4L2-PIX-FMT-NV12-10BE-8L128: .. _V4L2-PIX-FMT-MM21: Tiled NV12 @@ -319,6 +321,9 @@ pixels in 2D 8x128 tiles, and stores tiles linearly in memory. The image height must be aligned to a multiple of 128. The layouts of the luma and chroma planes are identical. +``V4L2_PIX_FMT_NV12_8L128`` is similar to ``V4L2_PIX_FMT_NV12M_8L128`` but stores +two planes in one memory. + ``V4L2_PIX_FMT_NV12M_10BE_8L128`` is similar to ``V4L2_PIX_FMT_NV12M`` but stores 10 bits pixels in 2D 8x128 tiles, and stores tiles linearly in memory. the data is arranged in big endian order. @@ -334,6 +339,9 @@ byte 2: Y1(bits 3-0) Y2(bits 9-6) byte 3: Y2(bits 5-0) Y3(bits 9-8) byte 4: Y3(bits 7-0) +``V4L2_PIX_FMT_NV12_10BE_8L128`` is similar to ``V4L2_PIX_FMT_NV12M_10BE_8L128`` but stores +two planes in one memory. + ``V4L2_PIX_FMT_MM21`` store luma pixel in 16x32 tiles, and chroma pixels in 16x16 tiles. The line stride must be aligned to a multiple of 16 and the image height must be aligned to a multiple of 32. The number of luma and chroma diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index d21d532eee15..16ef3b41932e 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -6057,6 +6057,43 @@ the following codes. - y\ :sub:`2` - y\ :sub:`1` - y\ :sub:`0` + * .. _MEDIA-BUS-FMT-Y16-1X16: + + - MEDIA_BUS_FMT_Y16_1X16 + - 0x202e + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - y\ :sub:`15` + - y\ :sub:`14` + - y\ :sub:`13` + - y\ :sub:`12` + - y\ :sub:`11` + - y\ :sub:`10` + - y\ :sub:`9` + - y\ :sub:`8` + - y\ :sub:`7` + - y\ :sub:`6` + - y\ :sub:`5` + - y\ :sub:`4` + - y\ :sub:`3` + - y\ :sub:`2` + - y\ :sub:`1` + - y\ :sub:`0` * .. _MEDIA-BUS-FMT-UYVY8-1X16: - MEDIA_BUS_FMT_UYVY8_1X16 diff --git a/MAINTAINERS b/MAINTAINERS index 070cd016fc4a..2d385aba488c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -775,6 +775,24 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/allwinner,sun4i-a10-csi.yaml F: drivers/media/platform/sunxi/sun4i-csi/ +ALLWINNER A31 CSI DRIVER +M: Yong Deng <yong.deng@magewell.com> +M: Paul Kocialkowski <paul.kocialkowski@bootlin.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/allwinner,sun6i-a31-csi.yaml +F: drivers/media/platform/sunxi/sun6i-csi/ + +ALLWINNER A31 ISP DRIVER +M: Paul Kocialkowski <paul.kocialkowski@bootlin.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/allwinner,sun6i-a31-isp.yaml +F: drivers/staging/media/sunxi/sun6i-isp/ +F: drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h + ALLWINNER A31 MIPI CSI-2 BRIDGE DRIVER M: Paul Kocialkowski <paul.kocialkowski@bootlin.com> L: linux-media@vger.kernel.org @@ -5521,14 +5539,6 @@ M: Jaya Kumar <jayakumar.alsa@gmail.com> S: Maintained F: sound/pci/cs5535audio/ -CSI DRIVERS FOR ALLWINNER V3s -M: Yong Deng <yong.deng@magewell.com> -L: linux-media@vger.kernel.org -S: Maintained -T: git git://linuxtv.org/media_tree.git -F: Documentation/devicetree/bindings/media/allwinner,sun6i-a31-csi.yaml -F: drivers/media/platform/sunxi/sun6i-csi/ - CTU CAN FD DRIVER M: Pavel Pisa <pisa@cmp.felk.cvut.cz> M: Ondrej Ille <ondrej.ille@gmail.com> @@ -12776,7 +12786,7 @@ F: Documentation/admin-guide/media/imx7.rst F: Documentation/devicetree/bindings/media/nxp,imx-mipi-csi2.yaml F: Documentation/devicetree/bindings/media/nxp,imx7-csi.yaml F: drivers/media/platform/nxp/imx-mipi-csis.c -F: drivers/staging/media/imx/imx7-media-csi.c +F: drivers/media/platform/nxp/imx7-media-csi.c MEDIA DRIVERS FOR HELENE M: Abylay Ospan <aospan@netup.ru> @@ -13516,7 +13526,7 @@ M: Eugen Hristev <eugen.hristev@microchip.com> L: linux-media@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/media/microchip,csi2dc.yaml -F: drivers/media/platform/atmel/microchip-csi2dc.c +F: drivers/media/platform/microchip/microchip-csi2dc.c MICROCHIP ECC DRIVER M: Tudor Ambarus <tudor.ambarus@microchip.com> @@ -13543,8 +13553,10 @@ L: linux-media@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/media/atmel,isc.yaml F: Documentation/devicetree/bindings/media/microchip,xisc.yaml -F: drivers/media/platform/atmel/atmel-isc* -F: drivers/media/platform/atmel/atmel-sama*-isc* +F: drivers/staging/media/deprecated/atmel/atmel-isc* +F: drivers/staging/media/deprecated/atmel/atmel-sama*-isc* +F: drivers/media/platform/microchip/microchip-isc* +F: drivers/media/platform/microchip/microchip-sama*-isc* F: include/linux/atmel-isc-media.h MICROCHIP ISI DRIVER @@ -15236,6 +15248,13 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov08d10.c +OMNIVISION OV08X40 SENSOR DRIVER +M: Jason Chen <jason.z.chen@intel.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: drivers/media/i2c/ov08x40.c + OMNIVISION OV13858 SENSOR DRIVER M: Sakari Ailus <sakari.ailus@linux.intel.com> L: linux-media@vger.kernel.org @@ -15274,6 +15293,14 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov2740.c +OMNIVISION OV4689 SENSOR DRIVER +M: Mikhail Rudenko <mike.rudenko@gmail.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml +F: drivers/media/i2c/ov5647.c + OMNIVISION OV5640 SENSOR DRIVER M: Steve Longerbeam <slongerbeam@gmail.com> L: linux-media@vger.kernel.org @@ -16757,7 +16784,6 @@ M: Hans Verkuil <hverkuil@xs4all.nl> L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git -F: Documentation/admin-guide/media/pulse8-cec.rst F: drivers/media/cec/usb/pulse8/ PURELIFI PLFXLC DRIVER @@ -17210,7 +17236,8 @@ F: Documentation/devicetree/bindings/thermal/qcom-tsens.yaml F: drivers/thermal/qcom/ QUALCOMM VENUS VIDEO ACCELERATOR DRIVER -M: Stanimir Varbanov <stanimir.varbanov@linaro.org> +M: Stanimir Varbanov <stanimir.k.varbanov@gmail.com> +M: Vikash Garodia <quic_vgarodia@quicinc.com> L: linux-media@vger.kernel.org L: linux-arm-msm@vger.kernel.org S: Maintained @@ -19260,7 +19287,7 @@ M: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git -F: Documentation/devicetree/bindings/media/i2c/imx290.txt +F: Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml F: drivers/media/i2c/imx290.c SONY IMX319 SENSOR DRIVER @@ -19580,6 +19607,16 @@ S: Maintained F: Documentation/hwmon/stpddc60.rst F: drivers/hwmon/pmbus/stpddc60.c +ST VGXY61 DRIVER +M: Benjamin Mugnier <benjamin.mugnier@foss.st.com> +M: Sylvain Petinot <sylvain.petinot@foss.st.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml +F: Documentation/userspace-api/media/drivers/st-vgxy61.rst +F: drivers/media/i2c/st-vgxy61.c + ST VL53L0X ToF RANGER(I2C) IIO DRIVER M: Song Qiang <songqiang1304521@gmail.com> L: linux-iio@vger.kernel.org @@ -19595,6 +19632,7 @@ S: Supported F: Documentation/process/stable-kernel-rules.rst STAGING - ATOMISP DRIVER +M: Hans de Goede <hdegoede@redhat.com> M: Mauro Carvalho Chehab <mchehab@kernel.org> R: Sakari Ailus <sakari.ailus@linux.intel.com> L: linux-media@vger.kernel.org @@ -21804,6 +21842,12 @@ F: include/linux/virtio*.h F: include/uapi/linux/virtio_*.h F: tools/virtio/ +VISL VIRTUAL STATELESS DECODER DRIVER +M: Daniel Almeida <daniel.almeida@collabora.com> +L: linux-media@vger.kernel.org +S: Supported +F: drivers/media/test-drivers/visl + IFCVF VIRTIO DATA PATH ACCELERATOR R: Zhu Lingshan <lingshan.zhu@intel.com> F: drivers/vdpa/ifcvf/ diff --git a/arch/arm/boot/dts/imx6qdl-pico.dtsi b/arch/arm/boot/dts/imx6qdl-pico.dtsi index f7a56d6b160c..c39a9ebdaba1 100644 --- a/arch/arm/boot/dts/imx6qdl-pico.dtsi +++ b/arch/arm/boot/dts/imx6qdl-pico.dtsi @@ -233,7 +233,6 @@ pinctrl-0 = <&pinctrl_ov5645>; reg = <0x3c>; clocks = <&clks IMX6QDL_CLK_CKO2>; - clock-names = "xclk"; clock-frequency = <24000000>; vdddo-supply = <®_1p8v>; vdda-supply = <®_2p8v>; diff --git a/arch/arm/boot/dts/imx6qdl-wandboard.dtsi b/arch/arm/boot/dts/imx6qdl-wandboard.dtsi index ec6fba5ee8fd..e4f63423d8ee 100644 --- a/arch/arm/boot/dts/imx6qdl-wandboard.dtsi +++ b/arch/arm/boot/dts/imx6qdl-wandboard.dtsi @@ -131,7 +131,6 @@ pinctrl-0 = <&pinctrl_ov5645>; reg = <0x3c>; clocks = <&clks IMX6QDL_CLK_CKO2>; - clock-names = "xclk"; clock-frequency = <24000000>; vdddo-supply = <®_1p8v>; vdda-supply = <®_2p8v>; diff --git a/arch/arm64/boot/dts/renesas/aistarvision-mipi-adapter-2.1.dtsi b/arch/arm64/boot/dts/renesas/aistarvision-mipi-adapter-2.1.dtsi index 7ce986f0a06f..7cb5c958aece 100644 --- a/arch/arm64/boot/dts/renesas/aistarvision-mipi-adapter-2.1.dtsi +++ b/arch/arm64/boot/dts/renesas/aistarvision-mipi-adapter-2.1.dtsi @@ -65,7 +65,6 @@ ov5645: ov5645@3c { compatible = "ovti,ov5645"; reg = <0x3c>; - clock-names = "xclk"; clocks = <&osc25250_clk>; clock-frequency = <24000000>; vdddo-supply = <&ov5645_vdddo_1v8>; diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 283b78b5766e..6abc9302cd84 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -182,7 +182,7 @@ config MEDIA_CONTROLLER # config DVB_CORE - tristate + tristate "DVB Core" depends on MEDIA_DIGITAL_TV_SUPPORT depends on (I2C || I2C=n) default MEDIA_DIGITAL_TV_SUPPORT diff --git a/drivers/media/cec/platform/stm32/stm32-cec.c b/drivers/media/cec/platform/stm32/stm32-cec.c index 40db7911b437..7b2db46a5722 100644 --- a/drivers/media/cec/platform/stm32/stm32-cec.c +++ b/drivers/media/cec/platform/stm32/stm32-cec.c @@ -288,12 +288,9 @@ static int stm32_cec_probe(struct platform_device *pdev) return ret; cec->clk_cec = devm_clk_get(&pdev->dev, "cec"); - if (IS_ERR(cec->clk_cec)) { - if (PTR_ERR(cec->clk_cec) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Cannot get cec clock\n"); - - return PTR_ERR(cec->clk_cec); - } + if (IS_ERR(cec->clk_cec)) + return dev_err_probe(&pdev->dev, PTR_ERR(cec->clk_cec), + "Cannot get cec clock\n"); ret = clk_prepare(cec->clk_cec); if (ret) { diff --git a/drivers/media/common/videobuf2/frame_vector.c b/drivers/media/common/videobuf2/frame_vector.c index 144027035892..89eebc3341b8 100644 --- a/drivers/media/common/videobuf2/frame_vector.c +++ b/drivers/media/common/videobuf2/frame_vector.c @@ -14,6 +14,7 @@ * get_vaddr_frames() - map virtual addresses to pfns * @start: starting user address * @nr_frames: number of pages / pfns from start to map + * @write: the mapped address has write permission * @vec: structure which receives pages / pfns of the addresses mapped. * It should have space for at least nr_frames entries. * @@ -32,10 +33,11 @@ * * This function takes care of grabbing mmap_lock as necessary. */ -int get_vaddr_frames(unsigned long start, unsigned int nr_frames, +int get_vaddr_frames(unsigned long start, unsigned int nr_frames, bool write, struct frame_vector *vec) { int ret; + unsigned int gup_flags = FOLL_FORCE | FOLL_LONGTERM; if (nr_frames == 0) return 0; @@ -45,8 +47,10 @@ int get_vaddr_frames(unsigned long start, unsigned int nr_frames, start = untagged_addr(start); - ret = pin_user_pages_fast(start, nr_frames, - FOLL_FORCE | FOLL_WRITE | FOLL_LONGTERM, + if (write) + gup_flags |= FOLL_WRITE; + + ret = pin_user_pages_fast(start, nr_frames, gup_flags, (struct page **)(vec->ptrs)); vec->got_ref = true; vec->is_pfns = false; diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c index 92efc4676df6..fc3758a5bc1c 100644 --- a/drivers/media/common/videobuf2/videobuf2-core.c +++ b/drivers/media/common/videobuf2/videobuf2-core.c @@ -544,6 +544,7 @@ static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) */ if (q->num_buffers) { bool unbalanced = q->cnt_start_streaming != q->cnt_stop_streaming || + q->cnt_prepare_streaming != q->cnt_unprepare_streaming || q->cnt_wait_prepare != q->cnt_wait_finish; if (unbalanced || debug) { @@ -552,14 +553,18 @@ static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) pr_info(" setup: %u start_streaming: %u stop_streaming: %u\n", q->cnt_queue_setup, q->cnt_start_streaming, q->cnt_stop_streaming); + pr_info(" prepare_streaming: %u unprepare_streaming: %u\n", + q->cnt_prepare_streaming, q->cnt_unprepare_streaming); pr_info(" wait_prepare: %u wait_finish: %u\n", q->cnt_wait_prepare, q->cnt_wait_finish); } q->cnt_queue_setup = 0; q->cnt_wait_prepare = 0; q->cnt_wait_finish = 0; + q->cnt_prepare_streaming = 0; q->cnt_start_streaming = 0; q->cnt_stop_streaming = 0; + q->cnt_unprepare_streaming = 0; } for (buffer = 0; buffer < q->num_buffers; ++buffer) { struct vb2_buffer *vb = q->bufs[buffer]; @@ -2026,6 +2031,9 @@ static void __vb2_queue_cancel(struct vb2_queue *q) if (q->start_streaming_called) call_void_qop(q, stop_streaming, q); + if (q->streaming) + call_void_qop(q, unprepare_streaming, q); + /* * If you see this warning, then the driver isn't cleaning up properly * in stop_streaming(). See the stop_streaming() documentation in @@ -2137,23 +2145,29 @@ int vb2_core_streamon(struct vb2_queue *q, unsigned int type) return -EINVAL; } + ret = call_qop(q, prepare_streaming, q); + if (ret) + return ret; + + q->streaming = 1; + /* * Tell driver to start streaming provided sufficient buffers * are available. */ if (q->queued_count >= q->min_buffers_needed) { - ret = v4l_vb2q_enable_media_source(q); - if (ret) - return ret; ret = vb2_start_streaming(q); if (ret) - return ret; + goto unprepare; } - q->streaming = 1; - dprintk(q, 3, "successful\n"); return 0; + +unprepare: + call_void_qop(q, unprepare_streaming, q); + q->streaming = 0; + return ret; } EXPORT_SYMBOL_GPL(vb2_core_streamon); diff --git a/drivers/media/common/videobuf2/videobuf2-dma-contig.c b/drivers/media/common/videobuf2/videobuf2-dma-contig.c index 678b359717c4..8e55468cb60d 100644 --- a/drivers/media/common/videobuf2/videobuf2-dma-contig.c +++ b/drivers/media/common/videobuf2/videobuf2-dma-contig.c @@ -603,7 +603,8 @@ static void *vb2_dc_get_userptr(struct vb2_buffer *vb, struct device *dev, buf->vb = vb; offset = lower_32_bits(offset_in_page(vaddr)); - vec = vb2_create_framevec(vaddr, size); + vec = vb2_create_framevec(vaddr, size, buf->dma_dir == DMA_FROM_DEVICE || + buf->dma_dir == DMA_BIDIRECTIONAL); if (IS_ERR(vec)) { ret = PTR_ERR(vec); goto fail_buf; diff --git a/drivers/media/common/videobuf2/videobuf2-dma-sg.c b/drivers/media/common/videobuf2/videobuf2-dma-sg.c index fa69158a65b1..099693e42bc6 100644 --- a/drivers/media/common/videobuf2/videobuf2-dma-sg.c +++ b/drivers/media/common/videobuf2/videobuf2-dma-sg.c @@ -241,7 +241,9 @@ static void *vb2_dma_sg_get_userptr(struct vb2_buffer *vb, struct device *dev, buf->size = size; buf->dma_sgt = &buf->sg_table; buf->vb = vb; - vec = vb2_create_framevec(vaddr, size); + vec = vb2_create_framevec(vaddr, size, + buf->dma_dir == DMA_FROM_DEVICE || + buf->dma_dir == DMA_BIDIRECTIONAL); if (IS_ERR(vec)) goto userptr_fail_pfnvec; buf->vec = vec; diff --git a/drivers/media/common/videobuf2/videobuf2-memops.c b/drivers/media/common/videobuf2/videobuf2-memops.c index 9dd6c27162f4..f9a4ec44422e 100644 --- a/drivers/media/common/videobuf2/videobuf2-memops.c +++ b/drivers/media/common/videobuf2/videobuf2-memops.c @@ -26,6 +26,7 @@ * vb2_create_framevec() - map virtual addresses to pfns * @start: Virtual user address where we start mapping * @length: Length of a range to map + * @write: Should we map for writing into the area * * This function allocates and fills in a vector with pfns corresponding to * virtual address range passed in arguments. If pfns have corresponding pages, @@ -34,7 +35,8 @@ * failure. Returned vector needs to be freed via vb2_destroy_pfnvec(). */ struct frame_vector *vb2_create_framevec(unsigned long start, - unsigned long length) + unsigned long length, + bool write) { int ret; unsigned long first, last; @@ -47,7 +49,7 @@ struct frame_vector *vb2_create_framevec(unsigned long start, vec = frame_vector_create(nr); if (!vec) return ERR_PTR(-ENOMEM); - ret = get_vaddr_frames(start & PAGE_MASK, nr, vec); + ret = get_vaddr_frames(start & PAGE_MASK, nr, write, vec); if (ret < 0) goto out_destroy; /* We accept only complete set of PFNs */ diff --git a/drivers/media/common/videobuf2/videobuf2-vmalloc.c b/drivers/media/common/videobuf2/videobuf2-vmalloc.c index 948152f1596b..67d0b89e701b 100644 --- a/drivers/media/common/videobuf2/videobuf2-vmalloc.c +++ b/drivers/media/common/videobuf2/videobuf2-vmalloc.c @@ -85,7 +85,9 @@ static void *vb2_vmalloc_get_userptr(struct vb2_buffer *vb, struct device *dev, buf->dma_dir = vb->vb2_queue->dma_dir; offset = vaddr & ~PAGE_MASK; buf->size = size; - vec = vb2_create_framevec(vaddr, size); + vec = vb2_create_framevec(vaddr, size, + buf->dma_dir == DMA_FROM_DEVICE || + buf->dma_dir == DMA_BIDIRECTIONAL); if (IS_ERR(vec)) { ret = PTR_ERR(vec); goto fail_pfnvec_create; diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c index f6ee678107d3..9ce5f010de3f 100644 --- a/drivers/media/dvb-core/dmxdev.c +++ b/drivers/media/dvb-core/dmxdev.c @@ -790,6 +790,11 @@ static int dvb_demux_open(struct inode *inode, struct file *file) if (mutex_lock_interruptible(&dmxdev->mutex)) return -ERESTARTSYS; + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + for (i = 0; i < dmxdev->filternum; i++) if (dmxdev->filter[i].state == DMXDEV_STATE_FREE) break; @@ -1448,7 +1453,10 @@ EXPORT_SYMBOL(dvb_dmxdev_init); void dvb_dmxdev_release(struct dmxdev *dmxdev) { + mutex_lock(&dmxdev->mutex); dmxdev->exit = 1; + mutex_unlock(&dmxdev->mutex); + if (dmxdev->dvbdev->users > 1) { wait_event(dmxdev->dvbdev->wait_queue, dmxdev->dvbdev->users == 1); diff --git a/drivers/media/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c index 15a08d8c69ef..c2d2792227f8 100644 --- a/drivers/media/dvb-core/dvb_ca_en50221.c +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -157,7 +157,7 @@ static void dvb_ca_private_free(struct dvb_ca_private *ca) { unsigned int i; - dvb_free_device(ca->dvbdev); + dvb_device_put(ca->dvbdev); for (i = 0; i < ca->slot_count; i++) vfree(ca->slot_info[i].rx_buffer.data); diff --git a/drivers/media/dvb-core/dvb_demux.c b/drivers/media/dvb-core/dvb_demux.c index 83cc32ad7e12..398c86279b5b 100644 --- a/drivers/media/dvb-core/dvb_demux.c +++ b/drivers/media/dvb-core/dvb_demux.c @@ -233,7 +233,7 @@ static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, { struct dvb_demux *demux = feed->demux; struct dmx_section_feed *sec = &feed->feed.sec; - u16 limit, seclen, n; + u16 limit, seclen; if (sec->tsfeedp >= DMX_MAX_SECFEED_SIZE) return 0; @@ -262,7 +262,7 @@ static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, /* to be sure always set secbuf */ sec->secbuf = sec->secbuf_base + sec->secbufp; - for (n = 0; sec->secbufp + 2 < limit; n++) { + while (sec->secbufp + 2 < limit) { seclen = section_length(sec->secbuf); if (seclen <= 0 || seclen > DMX_MAX_SECTION_SIZE || seclen + sec->secbufp > limit) diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c index 48e735cdbe6b..cc0a789f09ae 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -136,7 +136,7 @@ static void __dvb_frontend_free(struct dvb_frontend *fe) struct dvb_frontend_private *fepriv = fe->frontend_priv; if (fepriv) - dvb_free_device(fepriv->dvbdev); + dvb_device_put(fepriv->dvbdev); dvb_frontend_invoke_release(fe, fe->ops.release); @@ -918,6 +918,7 @@ static void dvb_frontend_get_frequency_limits(struct dvb_frontend *fe, /* If the standard is for satellite, convert frequencies to kHz */ switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -943,6 +944,7 @@ static u32 dvb_frontend_get_stepsize(struct dvb_frontend *fe) u32 step = max(fe_step, tuner_step); switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -974,6 +976,7 @@ static int dvb_frontend_check_parameters(struct dvb_frontend *fe) /* range check: symbol rate */ switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -1040,6 +1043,10 @@ static int dvb_frontend_clear_cache(struct dvb_frontend *fe) c->scrambling_sequence_index = 0;/* default sequence */ switch (c->delivery_system) { + case SYS_DSS: + c->modulation = QPSK; + c->rolloff = ROLLOFF_20; + break; case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -1821,6 +1828,7 @@ static void prepare_tuning_algo_parameters(struct dvb_frontend *fe) } else { /* default values */ switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_ISDBS: @@ -2288,6 +2296,9 @@ static int dtv_set_frontend(struct dvb_frontend *fe) case SYS_DVBC_ANNEX_C: rolloff = 113; break; + case SYS_DSS: + rolloff = 120; + break; case SYS_DVBS: case SYS_TURBO: case SYS_ISDBS: @@ -2754,7 +2765,17 @@ static int dvb_frontend_open(struct inode *inode, struct file *file) if (fe->exit == DVB_FE_DEVICE_REMOVED) return -ENODEV; - if (adapter->mfe_shared) { + if (adapter->mfe_shared == 2) { + mutex_lock(&adapter->mfe_lock); + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + if (adapter->mfe_dvbdev && + !adapter->mfe_dvbdev->writers) { + mutex_unlock(&adapter->mfe_lock); + return -EBUSY; + } + adapter->mfe_dvbdev = dvbdev; + } + } else if (adapter->mfe_shared) { mutex_lock(&adapter->mfe_lock); if (!adapter->mfe_dvbdev) @@ -2986,6 +3007,7 @@ int dvb_register_frontend(struct dvb_adapter *dvb, .name = fe->ops.info.name, #endif }; + int ret; dev_dbg(dvb->device, "%s:\n", __func__); @@ -3019,8 +3041,13 @@ int dvb_register_frontend(struct dvb_adapter *dvb, "DVB: registering adapter %i frontend %i (%s)...\n", fe->dvb->num, fe->id, fe->ops.info.name); - dvb_register_device(fe->dvb, &fepriv->dvbdev, &dvbdev_template, + ret = dvb_register_device(fe->dvb, &fepriv->dvbdev, &dvbdev_template, fe, DVB_DEVICE_FRONTEND, 0); + if (ret) { + dvb_frontend_put(fe); + mutex_unlock(&frontend_mutex); + return ret; + } /* * Initialize the cache to the proper values according with the diff --git a/drivers/media/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb-core/dvb_ringbuffer.c index d1d471af0636..7d4558de8e83 100644 --- a/drivers/media/dvb-core/dvb_ringbuffer.c +++ b/drivers/media/dvb-core/dvb_ringbuffer.c @@ -335,7 +335,9 @@ ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* idx = (idx + curpktlen + DVB_RINGBUFFER_PKTHDRSIZE) % rbuf->size; } - consumed = (idx - rbuf->pread) % rbuf->size; + consumed = (idx - rbuf->pread); + if (consumed < 0) + consumed += rbuf->size; while((dvb_ringbuffer_avail(rbuf) - consumed) > DVB_RINGBUFFER_PKTHDRSIZE) { diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbdev.c index 675d877a67b2..2a857cf70c94 100644 --- a/drivers/media/dvb-core/dvbdev.c +++ b/drivers/media/dvb-core/dvbdev.c @@ -97,7 +97,7 @@ static int dvb_device_open(struct inode *inode, struct file *file) new_fops = fops_get(dvbdev->fops); if (!new_fops) goto fail; - file->private_data = dvbdev; + file->private_data = dvb_device_get(dvbdev); replace_fops(file, new_fops); if (file->f_op->open) err = file->f_op->open(inode, file); @@ -161,6 +161,9 @@ int dvb_generic_release(struct inode *inode, struct file *file) } dvbdev->users++; + + dvb_device_put(dvbdev); + return 0; } EXPORT_SYMBOL(dvb_generic_release); @@ -243,7 +246,7 @@ static void dvb_media_device_free(struct dvb_device *dvbdev) static int dvb_create_tsout_entity(struct dvb_device *dvbdev, const char *name, int npads) { - int i, ret = 0; + int i; dvbdev->tsout_pads = kcalloc(npads, sizeof(*dvbdev->tsout_pads), GFP_KERNEL); @@ -260,6 +263,7 @@ static int dvb_create_tsout_entity(struct dvb_device *dvbdev, for (i = 0; i < npads; i++) { struct media_pad *pads = &dvbdev->tsout_pads[i]; struct media_entity *entity = &dvbdev->tsout_entity[i]; + int ret; entity->name = kasprintf(GFP_KERNEL, "%s #%d", name, i); if (!entity->name) @@ -332,6 +336,7 @@ static int dvb_create_media_entity(struct dvb_device *dvbdev, GFP_KERNEL); if (!dvbdev->pads) { kfree(dvbdev->entity); + dvbdev->entity = NULL; return -ENOMEM; } } @@ -478,6 +483,7 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, } memcpy(dvbdev, template, sizeof(struct dvb_device)); + kref_init(&dvbdev->ref); dvbdev->type = type; dvbdev->id = id; dvbdev->adapter = adap; @@ -508,7 +514,7 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, #endif dvbdev->minor = minor; - dvb_minors[minor] = dvbdev; + dvb_minors[minor] = dvb_device_get(dvbdev); up_write(&minor_rwsem); ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads); @@ -553,6 +559,7 @@ void dvb_remove_device(struct dvb_device *dvbdev) down_write(&minor_rwsem); dvb_minors[dvbdev->minor] = NULL; + dvb_device_put(dvbdev); up_write(&minor_rwsem); dvb_media_device_free(dvbdev); @@ -564,21 +571,34 @@ void dvb_remove_device(struct dvb_device *dvbdev) EXPORT_SYMBOL(dvb_remove_device); -void dvb_free_device(struct dvb_device *dvbdev) +static void dvb_free_device(struct kref *ref) { - if (!dvbdev) - return; + struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref); kfree (dvbdev->fops); kfree (dvbdev); } -EXPORT_SYMBOL(dvb_free_device); + + +struct dvb_device *dvb_device_get(struct dvb_device *dvbdev) +{ + kref_get(&dvbdev->ref); + return dvbdev; +} +EXPORT_SYMBOL(dvb_device_get); + + +void dvb_device_put(struct dvb_device *dvbdev) +{ + if (dvbdev) + kref_put(&dvbdev->ref, dvb_free_device); +} void dvb_unregister_device(struct dvb_device *dvbdev) { dvb_remove_device(dvbdev); - dvb_free_device(dvbdev); + dvb_device_put(dvbdev); } EXPORT_SYMBOL(dvb_unregister_device); diff --git a/drivers/media/dvb-frontends/a8293.c b/drivers/media/dvb-frontends/a8293.c index ba38783b2b4f..cca7cbdd4c7c 100644 --- a/drivers/media/dvb-frontends/a8293.c +++ b/drivers/media/dvb-frontends/a8293.c @@ -7,20 +7,148 @@ #include "a8293.h" +#define A8293_FLAG_ODT 0x10 + struct a8293_dev { struct i2c_client *client; u8 reg[2]; + int volt_slew_nanos_per_mv; }; -static int a8293_set_voltage(struct dvb_frontend *fe, - enum fe_sec_voltage fe_sec_voltage) +/* + * When increasing voltage, do so in minimal steps over time, minimizing + * risk of vIN undervoltage. + */ + +static int a8293_set_voltage_slew(struct a8293_dev *dev, + struct i2c_client *client, + enum fe_sec_voltage fe_sec_voltage, + int min_nanos_per_mv) +{ + int ret; + u8 reg0, reg1; + int new_volt_idx; + const int idx_to_mv[] = { + 0, 12709, 13042, 13375, 14042, 15042, 18042, 18709, 19042 + }; + const u8 idx_to_reg[] = { + 0x00, 0x20, 0x21, 0x22, 0x24, 0x27, 0x28, 0x2A, 0x2B + }; + int this_volt_idx; + u8 status; + int prev_volt_idx; + + dev_dbg(&client->dev, "set_voltage_slew fe_sec_voltage=%d\n", + fe_sec_voltage); + + /* Read status register to clear any stale faults. */ + ret = i2c_master_recv(client, &status, 1); + if (ret < 0) + goto err; + + /* Determine previous voltage */ + switch (dev->reg[0] & 0x2F) { + case 0x00: + prev_volt_idx = 0; + break; + case 0x20: + prev_volt_idx = 1; + break; + case 0x21: + prev_volt_idx = 2; + break; + case 0x22: + prev_volt_idx = 3; + break; + case 0x24: + prev_volt_idx = 4; + break; + case 0x27: + prev_volt_idx = 5; + break; + case 0x28: + prev_volt_idx = 6; + break; + case 0x2A: + prev_volt_idx = 7; + break; + case 0x2B: + prev_volt_idx = 8; + break; + default: + prev_volt_idx = 0; + } + + /* Determine new voltage */ + switch (fe_sec_voltage) { + case SEC_VOLTAGE_OFF: + new_volt_idx = 0; + break; + case SEC_VOLTAGE_13: + new_volt_idx = 2; + break; + case SEC_VOLTAGE_18: + new_volt_idx = 6; + break; + default: + ret = -EINVAL; + goto err; + } + + /* Slew to new voltage if new voltage is greater than current voltage */ + this_volt_idx = prev_volt_idx; + if (this_volt_idx < new_volt_idx) { + while (this_volt_idx < new_volt_idx) { + int delta_mv = idx_to_mv[this_volt_idx+1] - idx_to_mv[this_volt_idx]; + int min_wait_time = delta_mv * min_nanos_per_mv; + + reg0 = idx_to_reg[this_volt_idx+1]; + reg0 |= A8293_FLAG_ODT; + + ret = i2c_master_send(client, ®0, 1); + if (ret < 0) + goto err; + dev->reg[0] = reg0; + this_volt_idx++; + usleep_range(min_wait_time, min_wait_time * 2); + } + } else { /* Else just set the voltage */ + reg0 = idx_to_reg[new_volt_idx]; + reg0 |= A8293_FLAG_ODT; + ret = i2c_master_send(client, ®0, 1); + if (ret < 0) + goto err; + dev->reg[0] = reg0; + } + + /* TMODE=0, TGATE=1 */ + reg1 = 0x82; + if (reg1 != dev->reg[1]) { + ret = i2c_master_send(client, ®1, 1); + if (ret < 0) + goto err; + dev->reg[1] = reg1; + } + + usleep_range(1500, 5000); + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + + +static int a8293_set_voltage_noslew(struct dvb_frontend *fe, + enum fe_sec_voltage fe_sec_voltage) { struct a8293_dev *dev = fe->sec_priv; struct i2c_client *client = dev->client; int ret; u8 reg0, reg1; - dev_dbg(&client->dev, "fe_sec_voltage=%d\n", fe_sec_voltage); + dev_dbg(&client->dev, "set_voltage_noslew fe_sec_voltage=%d\n", + fe_sec_voltage); switch (fe_sec_voltage) { case SEC_VOLTAGE_OFF: @@ -62,8 +190,27 @@ err: return ret; } -static int a8293_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int a8293_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage fe_sec_voltage) +{ + struct a8293_dev *dev = fe->sec_priv; + struct i2c_client *client = dev->client; + int volt_slew_nanos_per_mv = dev->volt_slew_nanos_per_mv; + + dev_dbg(&client->dev, "set_voltage volt_slew_nanos_per_mv=%d\n", + volt_slew_nanos_per_mv); + + /* Use slew version if slew rate is set to a sane value */ + if (volt_slew_nanos_per_mv > 0 && volt_slew_nanos_per_mv < 1600) + a8293_set_voltage_slew(dev, client, fe_sec_voltage, + volt_slew_nanos_per_mv); + else + a8293_set_voltage_noslew(fe, fe_sec_voltage); + + return 0; +} + +static int a8293_probe(struct i2c_client *client) { struct a8293_dev *dev; struct a8293_platform_data *pdata = client->dev.platform_data; @@ -78,6 +225,7 @@ static int a8293_probe(struct i2c_client *client, } dev->client = client; + dev->volt_slew_nanos_per_mv = pdata->volt_slew_nanos_per_mv; /* check if the SEC is there */ ret = i2c_master_recv(client, buf, 2); @@ -118,7 +266,7 @@ static struct i2c_driver a8293_driver = { .name = "a8293", .suppress_bind_attrs = true, }, - .probe = a8293_probe, + .probe_new = a8293_probe, .remove = a8293_remove, .id_table = a8293_id_table, }; diff --git a/drivers/media/dvb-frontends/a8293.h b/drivers/media/dvb-frontends/a8293.h index 8c09635ef8a4..7fbac9735f7b 100644 --- a/drivers/media/dvb-frontends/a8293.h +++ b/drivers/media/dvb-frontends/a8293.h @@ -18,9 +18,12 @@ /** * struct a8293_platform_data - Platform data for the a8293 driver * @dvb_frontend: DVB frontend. + * @volt_slew_nanos_per_mv: Slew rate when increasing LNB voltage, + * in nanoseconds per millivolt. */ struct a8293_platform_data { struct dvb_frontend *dvb_frontend; + int volt_slew_nanos_per_mv; }; #endif /* A8293_H */ diff --git a/drivers/media/dvb-frontends/af9013.c b/drivers/media/dvb-frontends/af9013.c index d85929582c3f..206758a73ae2 100644 --- a/drivers/media/dvb-frontends/af9013.c +++ b/drivers/media/dvb-frontends/af9013.c @@ -1430,8 +1430,7 @@ err: return ret; } -static int af9013_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int af9013_probe(struct i2c_client *client) { struct af9013_state *state; struct af9013_platform_data *pdata = client->dev.platform_data; @@ -1564,7 +1563,7 @@ static struct i2c_driver af9013_driver = { .name = "af9013", .suppress_bind_attrs = true, }, - .probe = af9013_probe, + .probe_new = af9013_probe, .remove = af9013_remove, .id_table = af9013_id_table, }; diff --git a/drivers/media/dvb-frontends/af9033.c b/drivers/media/dvb-frontends/af9033.c index 808da7a9ffe7..a30773f62006 100644 --- a/drivers/media/dvb-frontends/af9033.c +++ b/drivers/media/dvb-frontends/af9033.c @@ -1049,8 +1049,7 @@ static const struct dvb_frontend_ops af9033_ops = { .i2c_gate_ctrl = af9033_i2c_gate_ctrl, }; -static int af9033_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int af9033_probe(struct i2c_client *client) { struct af9033_config *cfg = client->dev.platform_data; struct af9033_dev *dev; @@ -1184,7 +1183,7 @@ static struct i2c_driver af9033_driver = { .name = "af9033", .suppress_bind_attrs = true, }, - .probe = af9033_probe, + .probe_new = af9033_probe, .remove = af9033_remove, .id_table = af9033_id_table, }; diff --git a/drivers/media/dvb-frontends/au8522_decoder.c b/drivers/media/dvb-frontends/au8522_decoder.c index e4f99bd468cb..0f748cf46089 100644 --- a/drivers/media/dvb-frontends/au8522_decoder.c +++ b/drivers/media/dvb-frontends/au8522_decoder.c @@ -669,8 +669,7 @@ static const struct v4l2_ctrl_ops au8522_ctrl_ops = { /* ----------------------------------------------------------------------- */ -static int au8522_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int au8522_probe(struct i2c_client *client) { struct au8522_state *state; struct v4l2_ctrl_handler *hdl; @@ -777,7 +776,7 @@ static struct i2c_driver au8522_driver = { .driver = { .name = "au8522", }, - .probe = au8522_probe, + .probe_new = au8522_probe, .remove = au8522_remove, .id_table = au8522_id, }; diff --git a/drivers/media/dvb-frontends/bcm3510.c b/drivers/media/dvb-frontends/bcm3510.c index da0ff7b44da4..68b92b4419cf 100644 --- a/drivers/media/dvb-frontends/bcm3510.c +++ b/drivers/media/dvb-frontends/bcm3510.c @@ -649,6 +649,7 @@ static int bcm3510_download_firmware(struct dvb_frontend* fe) deb_info("firmware chunk, addr: 0x%04x, len: 0x%04x, total length: 0x%04zx\n",addr,len,fw->size); if ((ret = bcm3510_write_ram(st,addr,&b[i+4],len)) < 0) { err("firmware download failed: %d\n",ret); + release_firmware(fw); return ret; } i += 4 + len; diff --git a/drivers/media/dvb-frontends/cxd2099.c b/drivers/media/dvb-frontends/cxd2099.c index fbc666fa04ec..c0967ad95220 100644 --- a/drivers/media/dvb-frontends/cxd2099.c +++ b/drivers/media/dvb-frontends/cxd2099.c @@ -598,8 +598,7 @@ static const struct dvb_ca_en50221 en_templ = { .write_data = write_data, }; -static int cxd2099_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cxd2099_probe(struct i2c_client *client) { struct cxd *ci; struct cxd2099_cfg *cfg = client->dev.platform_data; @@ -682,7 +681,7 @@ static struct i2c_driver cxd2099_driver = { .driver = { .name = "cxd2099", }, - .probe = cxd2099_probe, + .probe_new = cxd2099_probe, .remove = cxd2099_remove, .id_table = cxd2099_id, }; diff --git a/drivers/media/dvb-frontends/cxd2820r_core.c b/drivers/media/dvb-frontends/cxd2820r_core.c index 5d98222f9df0..47aa40967171 100644 --- a/drivers/media/dvb-frontends/cxd2820r_core.c +++ b/drivers/media/dvb-frontends/cxd2820r_core.c @@ -547,8 +547,7 @@ static struct dvb_frontend *cxd2820r_get_dvb_frontend(struct i2c_client *client) return &priv->fe; } -static int cxd2820r_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cxd2820r_probe(struct i2c_client *client) { struct cxd2820r_platform_data *pdata = client->dev.platform_data; struct cxd2820r_priv *priv; @@ -629,7 +628,7 @@ static int cxd2820r_probe(struct i2c_client *client, /* * Chip has two I2C addresses for different register banks. We register - * one dummy I2C client in in order to get own I2C client for each + * one dummy I2C client in order to get own I2C client for each * register bank. */ priv->client[1] = i2c_new_dummy_device(client->adapter, client->addr | (1 << 1)); @@ -734,7 +733,7 @@ static struct i2c_driver cxd2820r_driver = { .name = "cxd2820r", .suppress_bind_attrs = true, }, - .probe = cxd2820r_probe, + .probe_new = cxd2820r_probe, .remove = cxd2820r_remove, .id_table = cxd2820r_id_table, }; diff --git a/drivers/media/dvb-frontends/cxd2820r_priv.h b/drivers/media/dvb-frontends/cxd2820r_priv.h index 09c42bcef971..9b4d9cf8563d 100644 --- a/drivers/media/dvb-frontends/cxd2820r_priv.h +++ b/drivers/media/dvb-frontends/cxd2820r_priv.h @@ -52,8 +52,6 @@ struct cxd2820r_priv { /* cxd2820r_core.c */ -extern int cxd2820r_debug; - int cxd2820r_gpio(struct dvb_frontend *fe, u8 *gpio); int cxd2820r_wr_reg_val_mask_tab(struct cxd2820r_priv *priv, diff --git a/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h b/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h index 739dc5590fa4..9df34c10d22b 100644 --- a/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h +++ b/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h @@ -234,8 +234,6 @@ /*-------- Public API functions ----------------------------------------------*/ -extern struct drx_access_func drx_dap_fasi_funct_g; - #define DRXDAP_FASI_RMW 0x10000000 #define DRXDAP_FASI_BROADCAST 0x20000000 #define DRXDAP_FASI_CLEARCRC 0x80000000 diff --git a/drivers/media/dvb-frontends/drx39xyj/drxj.c b/drivers/media/dvb-frontends/drx39xyj/drxj.c index bf9e4ef35684..1dff59ca21a1 100644 --- a/drivers/media/dvb-frontends/drx39xyj/drxj.c +++ b/drivers/media/dvb-frontends/drx39xyj/drxj.c @@ -2347,6 +2347,7 @@ hi_command(struct i2c_device_addr *dev_addr, const struct drxj_hi_cmd *cmd, u16 do { nr_retries++; if (nr_retries > DRXJ_MAX_RETRIES) { + rc = -ETIMEDOUT; pr_err("timeout\n"); goto rw_error; } diff --git a/drivers/media/dvb-frontends/helene.c b/drivers/media/dvb-frontends/helene.c index 8c1310c6b0bc..e4bbf6a51a2b 100644 --- a/drivers/media/dvb-frontends/helene.c +++ b/drivers/media/dvb-frontends/helene.c @@ -1063,8 +1063,7 @@ struct dvb_frontend *helene_attach(struct dvb_frontend *fe, } EXPORT_SYMBOL(helene_attach); -static int helene_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int helene_probe(struct i2c_client *client) { struct helene_config *config = client->dev.platform_data; struct dvb_frontend *fe = config->fe; @@ -1111,7 +1110,7 @@ static struct i2c_driver helene_driver = { .driver = { .name = "helene", }, - .probe = helene_probe, + .probe_new = helene_probe, .id_table = helene_id, }; module_i2c_driver(helene_driver); diff --git a/drivers/media/dvb-frontends/lgdt3306a.c b/drivers/media/dvb-frontends/lgdt3306a.c index 424311afb2bf..6bf723b5ffad 100644 --- a/drivers/media/dvb-frontends/lgdt3306a.c +++ b/drivers/media/dvb-frontends/lgdt3306a.c @@ -2169,8 +2169,7 @@ static int lgdt3306a_deselect(struct i2c_mux_core *muxc, u32 chan) return lgdt3306a_i2c_gate_ctrl(&state->frontend, 0); } -static int lgdt3306a_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int lgdt3306a_probe(struct i2c_client *client) { struct lgdt3306a_config *config; struct lgdt3306a_state *state; @@ -2250,7 +2249,7 @@ static struct i2c_driver lgdt3306a_driver = { .name = "lgdt3306a", .suppress_bind_attrs = true, }, - .probe = lgdt3306a_probe, + .probe_new = lgdt3306a_probe, .remove = lgdt3306a_remove, .id_table = lgdt3306a_id_table, }; diff --git a/drivers/media/dvb-frontends/lgdt330x.c b/drivers/media/dvb-frontends/lgdt330x.c index ea9ae22fd201..1d6932d8e497 100644 --- a/drivers/media/dvb-frontends/lgdt330x.c +++ b/drivers/media/dvb-frontends/lgdt330x.c @@ -857,8 +857,7 @@ static struct dvb_frontend *lgdt330x_get_dvb_frontend(struct i2c_client *client) static const struct dvb_frontend_ops lgdt3302_ops; static const struct dvb_frontend_ops lgdt3303_ops; -static int lgdt330x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int lgdt330x_probe(struct i2c_client *client) { struct lgdt330x_state *state = NULL; u8 buf[1]; @@ -994,7 +993,7 @@ static struct i2c_driver lgdt330x_driver = { .name = "lgdt330x", .suppress_bind_attrs = true, }, - .probe = lgdt330x_probe, + .probe_new = lgdt330x_probe, .remove = lgdt330x_remove, .id_table = lgdt330x_id_table, }; diff --git a/drivers/media/dvb-frontends/mn88472.c b/drivers/media/dvb-frontends/mn88472.c index 2b01cc678f7e..4a71f1c6371a 100644 --- a/drivers/media/dvb-frontends/mn88472.c +++ b/drivers/media/dvb-frontends/mn88472.c @@ -572,8 +572,7 @@ static struct dvb_frontend *mn88472_get_dvb_frontend(struct i2c_client *client) return &dev->fe; } -static int mn88472_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mn88472_probe(struct i2c_client *client) { struct mn88472_config *pdata = client->dev.platform_data; struct mn88472_dev *dev; @@ -719,7 +718,7 @@ static struct i2c_driver mn88472_driver = { .name = "mn88472", .suppress_bind_attrs = true, }, - .probe = mn88472_probe, + .probe_new = mn88472_probe, .remove = mn88472_remove, .id_table = mn88472_id_table, }; diff --git a/drivers/media/dvb-frontends/mn88473.c b/drivers/media/dvb-frontends/mn88473.c index f0ecf5910c02..205b14ae584e 100644 --- a/drivers/media/dvb-frontends/mn88473.c +++ b/drivers/media/dvb-frontends/mn88473.c @@ -606,8 +606,7 @@ static const struct dvb_frontend_ops mn88473_ops = { .read_status = mn88473_read_status, }; -static int mn88473_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mn88473_probe(struct i2c_client *client) { struct mn88473_config *config = client->dev.platform_data; struct mn88473_dev *dev; @@ -754,7 +753,7 @@ static struct i2c_driver mn88473_driver = { .name = "mn88473", .suppress_bind_attrs = true, }, - .probe = mn88473_probe, + .probe_new = mn88473_probe, .remove = mn88473_remove, .id_table = mn88473_id_table, }; diff --git a/drivers/media/dvb-frontends/mxl5xx.c b/drivers/media/dvb-frontends/mxl5xx.c index 934d1c0b214a..4ebbcf05cc09 100644 --- a/drivers/media/dvb-frontends/mxl5xx.c +++ b/drivers/media/dvb-frontends/mxl5xx.c @@ -1644,8 +1644,6 @@ static int validate_sku(struct mxl *state) default: return -1; } - } else { - } return -1; } diff --git a/drivers/media/dvb-frontends/mxl692.c b/drivers/media/dvb-frontends/mxl692.c index 129630cbffff..9858e11943a0 100644 --- a/drivers/media/dvb-frontends/mxl692.c +++ b/drivers/media/dvb-frontends/mxl692.c @@ -1308,8 +1308,7 @@ static const struct dvb_frontend_ops mxl692_ops = { .read_snr = mxl692_read_snr, }; -static int mxl692_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mxl692_probe(struct i2c_client *client) { struct mxl692_config *config = client->dev.platform_data; struct mxl692_dev *dev; @@ -1356,7 +1355,7 @@ static struct i2c_driver mxl692_driver = { .driver = { .name = "mxl692", }, - .probe = mxl692_probe, + .probe_new = mxl692_probe, .remove = mxl692_remove, .id_table = mxl692_id_table, }; diff --git a/drivers/media/dvb-frontends/rtl2830.c b/drivers/media/dvb-frontends/rtl2830.c index e0fbf41316ae..db3254950147 100644 --- a/drivers/media/dvb-frontends/rtl2830.c +++ b/drivers/media/dvb-frontends/rtl2830.c @@ -768,8 +768,7 @@ static int rtl2830_regmap_gather_write(void *context, const void *reg, return 0; } -static int rtl2830_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int rtl2830_probe(struct i2c_client *client) { struct rtl2830_platform_data *pdata = client->dev.platform_data; struct rtl2830_dev *dev; @@ -887,7 +886,7 @@ static struct i2c_driver rtl2830_driver = { .name = "rtl2830", .suppress_bind_attrs = true, }, - .probe = rtl2830_probe, + .probe_new = rtl2830_probe, .remove = rtl2830_remove, .id_table = rtl2830_id_table, }; diff --git a/drivers/media/dvb-frontends/rtl2832.c b/drivers/media/dvb-frontends/rtl2832.c index 4fa884eda5d5..900d4db8b922 100644 --- a/drivers/media/dvb-frontends/rtl2832.c +++ b/drivers/media/dvb-frontends/rtl2832.c @@ -1021,8 +1021,7 @@ err: return ret; } -static int rtl2832_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int rtl2832_probe(struct i2c_client *client) { struct rtl2832_platform_data *pdata = client->dev.platform_data; struct i2c_adapter *i2c = client->adapter; @@ -1136,7 +1135,7 @@ static struct i2c_driver rtl2832_driver = { .name = "rtl2832", .suppress_bind_attrs = true, }, - .probe = rtl2832_probe, + .probe_new = rtl2832_probe, .remove = rtl2832_remove, .id_table = rtl2832_id_table, }; diff --git a/drivers/media/dvb-frontends/si2165.c b/drivers/media/dvb-frontends/si2165.c index 86b0d59169dd..cc07e965c34c 100644 --- a/drivers/media/dvb-frontends/si2165.c +++ b/drivers/media/dvb-frontends/si2165.c @@ -1144,8 +1144,7 @@ static const struct dvb_frontend_ops si2165_ops = { .read_ber = si2165_read_ber, }; -static int si2165_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int si2165_probe(struct i2c_client *client) { struct si2165_state *state = NULL; struct si2165_platform_data *pdata = client->dev.platform_data; @@ -1293,7 +1292,7 @@ static struct i2c_driver si2165_driver = { .driver = { .name = "si2165", }, - .probe = si2165_probe, + .probe_new = si2165_probe, .remove = si2165_remove, .id_table = si2165_id_table, }; diff --git a/drivers/media/dvb-frontends/si2168.c b/drivers/media/dvb-frontends/si2168.c index 8157df4570d1..2a0e108c5eb0 100644 --- a/drivers/media/dvb-frontends/si2168.c +++ b/drivers/media/dvb-frontends/si2168.c @@ -672,8 +672,7 @@ static const struct dvb_frontend_ops si2168_ops = { .read_status = si2168_read_status, }; -static int si2168_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int si2168_probe(struct i2c_client *client) { struct si2168_config *config = client->dev.platform_data; struct si2168_dev *dev; @@ -799,7 +798,7 @@ static struct i2c_driver si2168_driver = { .name = "si2168", .suppress_bind_attrs = true, }, - .probe = si2168_probe, + .probe_new = si2168_probe, .remove = si2168_remove, .id_table = si2168_id_table, }; diff --git a/drivers/media/dvb-frontends/sp2.c b/drivers/media/dvb-frontends/sp2.c index 27e7037e130e..3395f6b5b948 100644 --- a/drivers/media/dvb-frontends/sp2.c +++ b/drivers/media/dvb-frontends/sp2.c @@ -363,8 +363,7 @@ static int sp2_exit(struct i2c_client *client) return 0; } -static int sp2_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int sp2_probe(struct i2c_client *client) { struct sp2_config *cfg = client->dev.platform_data; struct sp2 *s; @@ -417,7 +416,7 @@ static struct i2c_driver sp2_driver = { .driver = { .name = "sp2", }, - .probe = sp2_probe, + .probe_new = sp2_probe, .remove = sp2_remove, .id_table = sp2_id, }; diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c index 0a600c1d7d1b..9bde0ad6f26e 100644 --- a/drivers/media/dvb-frontends/stv090x.c +++ b/drivers/media/dvb-frontends/stv090x.c @@ -4990,8 +4990,7 @@ static struct dvb_frontend *stv090x_get_dvb_frontend(struct i2c_client *client) return &state->frontend; } -static int stv090x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int stv090x_probe(struct i2c_client *client) { int ret = 0; struct stv090x_config *config = client->dev.platform_data; @@ -5085,7 +5084,7 @@ static struct i2c_driver stv090x_driver = { .name = "stv090x", .suppress_bind_attrs = true, }, - .probe = stv090x_probe, + .probe_new = stv090x_probe, .remove = stv090x_remove, .id_table = stv090x_id_table, }; diff --git a/drivers/media/dvb-frontends/stv6110x.c b/drivers/media/dvb-frontends/stv6110x.c index fbc4dbd62151..b2f456116c60 100644 --- a/drivers/media/dvb-frontends/stv6110x.c +++ b/drivers/media/dvb-frontends/stv6110x.c @@ -406,8 +406,7 @@ static struct stv6110x_devctl *stv6110x_get_devctl(struct i2c_client *client) return stv6110x->devctl; } -static int stv6110x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int stv6110x_probe(struct i2c_client *client) { struct stv6110x_config *config = client->dev.platform_data; @@ -481,7 +480,7 @@ static struct i2c_driver stv6110x_driver = { .name = "stv6110x", .suppress_bind_attrs = true, }, - .probe = stv6110x_probe, + .probe_new = stv6110x_probe, .remove = stv6110x_remove, .id_table = stv6110x_id_table, }; diff --git a/drivers/media/dvb-frontends/tda10071.c b/drivers/media/dvb-frontends/tda10071.c index d1098ef20a8b..c8e5617d08c0 100644 --- a/drivers/media/dvb-frontends/tda10071.c +++ b/drivers/media/dvb-frontends/tda10071.c @@ -1145,8 +1145,7 @@ static struct dvb_frontend *tda10071_get_dvb_frontend(struct i2c_client *client) return &dev->fe; } -static int tda10071_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda10071_probe(struct i2c_client *client) { struct tda10071_dev *dev; struct tda10071_platform_data *pdata = client->dev.platform_data; @@ -1241,7 +1240,7 @@ static struct i2c_driver tda10071_driver = { .name = "tda10071", .suppress_bind_attrs = true, }, - .probe = tda10071_probe, + .probe_new = tda10071_probe, .remove = tda10071_remove, .id_table = tda10071_id_table, }; diff --git a/drivers/media/dvb-frontends/ts2020.c b/drivers/media/dvb-frontends/ts2020.c index 02338256b974..c28fee7509cd 100644 --- a/drivers/media/dvb-frontends/ts2020.c +++ b/drivers/media/dvb-frontends/ts2020.c @@ -550,8 +550,7 @@ static void ts2020_regmap_unlock(void *__dev) mutex_unlock(&dev->regmap_mutex); } -static int ts2020_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ts2020_probe(struct i2c_client *client) { struct ts2020_config *pdata = client->dev.platform_data; struct dvb_frontend *fe = pdata->fe; @@ -721,7 +720,7 @@ static struct i2c_driver ts2020_driver = { .driver = { .name = "ts2020", }, - .probe = ts2020_probe, + .probe_new = ts2020_probe, .remove = ts2020_remove, .id_table = ts2020_id_table, }; diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7806d4b81716..833241897d63 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -364,6 +364,19 @@ config VIDEO_OV08D10 To compile this driver as a module, choose M here: the module will be called ov08d10. +config VIDEO_OV08X40 + tristate "OmniVision OV08X40 sensor support" + depends on VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV08X40 camera. + + To compile this driver as a module, choose M here: the + module will be called ov08x40. + config VIDEO_OV13858 tristate "OmniVision OV13858 sensor support" depends on I2C && VIDEO_DEV @@ -445,6 +458,19 @@ config VIDEO_OV2740 To compile this driver as a module, choose M here: the module will be called ov2740. +config VIDEO_OV4689 + tristate "OmniVision OV4689 sensor support" + depends on GPIOLIB && VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor-level driver for the OmniVision + OV4689 camera. + + To compile this driver as a module, choose M here: the + module will be called ov4689. + config VIDEO_OV5640 tristate "OmniVision OV5640 sensor support" depends on OF @@ -725,16 +751,6 @@ config VIDEO_S5C73M3 This is a V4L2 sensor driver for Samsung S5C73M3 8 Mpixel camera. -config VIDEO_S5K4ECGX - tristate "Samsung S5K4ECGX sensor support" - depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select CRC32 - help - This is a V4L2 sensor driver for Samsung S5K4ECGX 5M - camera sensor with an embedded SoC image signal processor. - config VIDEO_S5K5BAF tristate "Samsung S5K5BAF sensor support" depends on I2C && VIDEO_DEV @@ -769,6 +785,16 @@ config VIDEO_SR030PC30 help This driver supports SR030PC30 VGA camera from Siliconfile +config VIDEO_ST_VGXY61 + tristate "ST VGXY61 sensor support" + depends on OF && GPIOLIB && VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the ST VGXY61 + camera sensor. + config VIDEO_VS6624 tristate "ST VS6624 sensor support" depends on VIDEO_DEV && I2C @@ -1272,6 +1298,22 @@ config VIDEO_TC358743_CEC When selected the tc358743 will support the optional HDMI CEC feature. +config VIDEO_TC358746 + tristate "Toshiba TC358746 parallel-CSI2 bridge" + depends on VIDEO_DEV && PM && I2C + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select V4L2_FWNODE + select GENERIC_PHY_MIPI_DPHY + select REGMAP_I2C + help + Support for the Toshiba TC358746 parallel to MIPI CSI-2 bridge. + The bridge can work in both directions but currently only the + parallel-in / csi-out path is supported. + + To compile this driver as a module, choose M here: the + module will be called tc358746. + config VIDEO_TVP514X tristate "Texas Instruments TVP514x video decoder" depends on VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..4d6c052bb5a7 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o +obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o obj-$(CONFIG_VIDEO_OV2640) += ov2640.o @@ -79,6 +80,7 @@ obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_OV2680) += ov2680.o obj-$(CONFIG_VIDEO_OV2685) += ov2685.o obj-$(CONFIG_VIDEO_OV2740) += ov2740.o +obj-$(CONFIG_VIDEO_OV4689) += ov4689.o obj-$(CONFIG_VIDEO_OV5640) += ov5640.o obj-$(CONFIG_VIDEO_OV5645) += ov5645.o obj-$(CONFIG_VIDEO_OV5647) += ov5647.o @@ -103,7 +105,6 @@ obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/ -obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o @@ -117,7 +118,9 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o +obj-$(CONFIG_VIDEO_TC358746) += tc358746.o obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o diff --git a/drivers/media/i2c/ad5820.c b/drivers/media/i2c/ad5820.c index 516de278cc49..44c26af49071 100644 --- a/drivers/media/i2c/ad5820.c +++ b/drivers/media/i2c/ad5820.c @@ -290,8 +290,7 @@ static int __maybe_unused ad5820_resume(struct device *dev) return ad5820_power_on(coil, true); } -static int ad5820_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int ad5820_probe(struct i2c_client *client) { struct ad5820_device *coil; int ret; @@ -301,21 +300,15 @@ static int ad5820_probe(struct i2c_client *client, return -ENOMEM; coil->vana = devm_regulator_get(&client->dev, "VANA"); - if (IS_ERR(coil->vana)) { - ret = PTR_ERR(coil->vana); - if (ret != -EPROBE_DEFER) - dev_err(&client->dev, "could not get regulator for vana\n"); - return ret; - } + if (IS_ERR(coil->vana)) + return dev_err_probe(&client->dev, PTR_ERR(coil->vana), + "could not get regulator for vana\n"); coil->enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW); - if (IS_ERR(coil->enable_gpio)) { - ret = PTR_ERR(coil->enable_gpio); - if (ret != -EPROBE_DEFER) - dev_err(&client->dev, "could not get enable gpio\n"); - return ret; - } + if (IS_ERR(coil->enable_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(coil->enable_gpio), + "could not get enable gpio\n"); mutex_init(&coil->power_lock); @@ -327,18 +320,18 @@ static int ad5820_probe(struct i2c_client *client, ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL); if (ret < 0) - goto cleanup2; + goto clean_mutex; ret = v4l2_async_register_subdev(&coil->subdev); if (ret < 0) - goto cleanup; + goto clean_entity; return ret; -cleanup2: - mutex_destroy(&coil->power_lock); -cleanup: +clean_entity: media_entity_cleanup(&coil->subdev.entity); +clean_mutex: + mutex_destroy(&coil->power_lock); return ret; } @@ -377,7 +370,7 @@ static struct i2c_driver ad5820_i2c_driver = { .pm = &ad5820_pm, .of_match_table = ad5820_of_table, }, - .probe = ad5820_probe, + .probe_new = ad5820_probe, .remove = ad5820_remove, .id_table = ad5820_id_table, }; diff --git a/drivers/media/i2c/ad9389b.c b/drivers/media/i2c/ad9389b.c index 4a255a492918..ad17097a2d25 100644 --- a/drivers/media/i2c/ad9389b.c +++ b/drivers/media/i2c/ad9389b.c @@ -1080,7 +1080,7 @@ static void ad9389b_init_setup(struct v4l2_subdev *sd) ad9389b_set_isr(sd, false); } -static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int ad9389b_probe(struct i2c_client *client) { const struct v4l2_dv_timings dv1080p60 = V4L2_DV_BT_CEA_1920X1080P60; struct ad9389b_state *state; @@ -1207,7 +1207,7 @@ static struct i2c_driver ad9389b_driver = { .driver = { .name = "ad9389b", }, - .probe = ad9389b_probe, + .probe_new = ad9389b_probe, .remove = ad9389b_remove, .id_table = ad9389b_id, }; diff --git a/drivers/media/i2c/adp1653.c b/drivers/media/i2c/adp1653.c index 1f353157df07..a61a77de6eee 100644 --- a/drivers/media/i2c/adp1653.c +++ b/drivers/media/i2c/adp1653.c @@ -463,8 +463,7 @@ err: } -static int adp1653_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int adp1653_probe(struct i2c_client *client) { struct adp1653_flash *flash; int ret; @@ -536,7 +535,7 @@ static struct i2c_driver adp1653_i2c_driver = { .name = ADP1653_NAME, .pm = &adp1653_pm_ops, }, - .probe = adp1653_probe, + .probe_new = adp1653_probe, .remove = adp1653_remove, .id_table = adp1653_id_table, }; diff --git a/drivers/media/i2c/adv7170.c b/drivers/media/i2c/adv7170.c index 61a2f87d3c62..aa0f80e299b3 100644 --- a/drivers/media/i2c/adv7170.c +++ b/drivers/media/i2c/adv7170.c @@ -334,8 +334,7 @@ static const struct v4l2_subdev_ops adv7170_ops = { /* ----------------------------------------------------------------------- */ -static int adv7170_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7170_probe(struct i2c_client *client) { struct adv7170 *encoder; struct v4l2_subdev *sd; @@ -388,7 +387,7 @@ static struct i2c_driver adv7170_driver = { .driver = { .name = "adv7170", }, - .probe = adv7170_probe, + .probe_new = adv7170_probe, .remove = adv7170_remove, .id_table = adv7170_id, }; diff --git a/drivers/media/i2c/adv7175.c b/drivers/media/i2c/adv7175.c index b58689728243..d9bea2b9ec33 100644 --- a/drivers/media/i2c/adv7175.c +++ b/drivers/media/i2c/adv7175.c @@ -389,8 +389,7 @@ static const struct v4l2_subdev_ops adv7175_ops = { /* ----------------------------------------------------------------------- */ -static int adv7175_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7175_probe(struct i2c_client *client) { int i; struct adv7175 *encoder; @@ -443,7 +442,7 @@ static struct i2c_driver adv7175_driver = { .driver = { .name = "adv7175", }, - .probe = adv7175_probe, + .probe_new = adv7175_probe, .remove = adv7175_remove, .id_table = adv7175_id, }; diff --git a/drivers/media/i2c/adv7183.c b/drivers/media/i2c/adv7183.c index 313c706e8335..98b63d79d33d 100644 --- a/drivers/media/i2c/adv7183.c +++ b/drivers/media/i2c/adv7183.c @@ -521,8 +521,7 @@ static const struct v4l2_subdev_ops adv7183_ops = { .pad = &adv7183_pad_ops, }; -static int adv7183_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7183_probe(struct i2c_client *client) { struct adv7183 *decoder; struct v4l2_subdev *sd; @@ -632,7 +631,7 @@ static struct i2c_driver adv7183_driver = { .driver = { .name = "adv7183", }, - .probe = adv7183_probe, + .probe_new = adv7183_probe, .remove = adv7183_remove, .id_table = adv7183_id, }; diff --git a/drivers/media/i2c/adv7393.c b/drivers/media/i2c/adv7393.c index fb5fefa83b18..61e916cbe651 100644 --- a/drivers/media/i2c/adv7393.c +++ b/drivers/media/i2c/adv7393.c @@ -381,8 +381,7 @@ static int adv7393_initialize(struct v4l2_subdev *sd) return err; } -static int adv7393_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7393_probe(struct i2c_client *client) { struct adv7393_state *state; int err; @@ -456,7 +455,7 @@ static struct i2c_driver adv7393_driver = { .driver = { .name = "adv7393", }, - .probe = adv7393_probe, + .probe_new = adv7393_probe, .remove = adv7393_remove, .id_table = adv7393_id, }; diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c index 02eabe10ab97..00095c7762c2 100644 --- a/drivers/media/i2c/adv748x/adv748x-afe.c +++ b/drivers/media/i2c/adv748x/adv748x-afe.c @@ -521,6 +521,10 @@ int adv748x_afe_init(struct adv748x_afe *afe) } } + adv748x_afe_s_input(afe, afe->input); + + adv_dbg(state, "AFE Default input set to %d\n", afe->input); + /* Entity pads and sinks are 0-indexed to match the pads */ for (i = ADV748X_AFE_SINK_AIN0; i <= ADV748X_AFE_SINK_AIN7; i++) afe->pads[i].flags = MEDIA_PAD_FL_SINK; diff --git a/drivers/media/i2c/adv748x/adv748x.h b/drivers/media/i2c/adv748x/adv748x.h index d75eb3d8be5a..6f90f78f58cf 100644 --- a/drivers/media/i2c/adv748x/adv748x.h +++ b/drivers/media/i2c/adv748x/adv748x.h @@ -428,9 +428,6 @@ void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, const struct v4l2_subdev_ops *ops, u32 function, const char *ident); -int adv748x_register_subdevs(struct adv748x_state *state, - struct v4l2_device *v4l2_dev); - int adv748x_tx_power(struct adv748x_csi2 *tx, bool on); int adv748x_afe_init(struct adv748x_afe *afe); diff --git a/drivers/media/i2c/adv7511-v4l2.c b/drivers/media/i2c/adv7511-v4l2.c index 0d5ce69f12e7..3999fa524cab 100644 --- a/drivers/media/i2c/adv7511-v4l2.c +++ b/drivers/media/i2c/adv7511-v4l2.c @@ -1763,7 +1763,7 @@ static void adv7511_init_setup(struct v4l2_subdev *sd) adv7511_cec_write(sd, 0x4e, ratio << 2); } -static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int adv7511_probe(struct i2c_client *client) { struct adv7511_state *state; struct adv7511_platform_data *pdata = client->dev.platform_data; @@ -1957,7 +1957,7 @@ static struct i2c_driver adv7511_driver = { .driver = { .name = "adv7511-v4l2", }, - .probe = adv7511_probe, + .probe_new = adv7511_probe, .remove = adv7511_remove, .id_table = adv7511_id, }; diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index 7731cc1887e6..cb8655574119 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -3441,8 +3441,7 @@ static int adv7842_register_clients(struct v4l2_subdev *sd) return 0; } -static int adv7842_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7842_probe(struct i2c_client *client) { struct adv7842_state *state; static const struct v4l2_dv_timings cea640x480 = @@ -3620,7 +3619,7 @@ static struct i2c_driver adv7842_driver = { .driver = { .name = "adv7842", }, - .probe = adv7842_probe, + .probe_new = adv7842_probe, .remove = adv7842_remove, .id_table = adv7842_id, }; diff --git a/drivers/media/i2c/ak881x.c b/drivers/media/i2c/ak881x.c index 0370ad6b6811..7c9ab76e2448 100644 --- a/drivers/media/i2c/ak881x.c +++ b/drivers/media/i2c/ak881x.c @@ -226,8 +226,7 @@ static const struct v4l2_subdev_ops ak881x_subdev_ops = { .pad = &ak881x_subdev_pad_ops, }; -static int ak881x_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ak881x_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct ak881x *ak881x; @@ -315,7 +314,7 @@ static struct i2c_driver ak881x_i2c_driver = { .driver = { .name = "ak881x", }, - .probe = ak881x_probe, + .probe_new = ak881x_probe, .remove = ak881x_remove, .id_table = ak881x_id, }; diff --git a/drivers/media/i2c/aptina-pll.c b/drivers/media/i2c/aptina-pll.c index 1423c04a1c90..b1f89bbf9d47 100644 --- a/drivers/media/i2c/aptina-pll.c +++ b/drivers/media/i2c/aptina-pll.c @@ -8,7 +8,6 @@ #include <linux/device.h> #include <linux/gcd.h> #include <linux/kernel.h> -#include <linux/lcm.h> #include <linux/module.h> #include "aptina-pll.h" diff --git a/drivers/media/i2c/ar0521.c b/drivers/media/i2c/ar0521.c index e408049f6312..77f597571167 100644 --- a/drivers/media/i2c/ar0521.c +++ b/drivers/media/i2c/ar0521.c @@ -14,26 +14,39 @@ #include <media/v4l2-subdev.h> /* External clock (extclk) frequencies */ -#define AR0521_EXTCLK_MIN (10 * 1000 * 1000) -#define AR0521_EXTCLK_MAX (48 * 1000 * 1000) +#define AR0521_EXTCLK_MIN (10 * 1000 * 1000) +#define AR0521_EXTCLK_MAX (48 * 1000 * 1000) /* PLL and PLL2 */ -#define AR0521_PLL_MIN (320 * 1000 * 1000) -#define AR0521_PLL_MAX (1280 * 1000 * 1000) - -/* Effective pixel clocks, the registers may be DDR */ -#define AR0521_PIXEL_CLOCK_RATE (184 * 1000 * 1000) -#define AR0521_PIXEL_CLOCK_MIN (168 * 1000 * 1000) -#define AR0521_PIXEL_CLOCK_MAX (414 * 1000 * 1000) - -#define AR0521_WIDTH_MIN 8u -#define AR0521_WIDTH_MAX 2608u -#define AR0521_HEIGHT_MIN 8u -#define AR0521_HEIGHT_MAX 1958u - -#define AR0521_WIDTH_BLANKING_MIN 572u -#define AR0521_HEIGHT_BLANKING_MIN 38u /* must be even */ -#define AR0521_TOTAL_WIDTH_MIN 2968u +#define AR0521_PLL_MIN (320 * 1000 * 1000) +#define AR0521_PLL_MAX (1280 * 1000 * 1000) + +/* Effective pixel sample rate on the pixel array. */ +#define AR0521_PIXEL_CLOCK_RATE (184 * 1000 * 1000) +#define AR0521_PIXEL_CLOCK_MIN (168 * 1000 * 1000) +#define AR0521_PIXEL_CLOCK_MAX (414 * 1000 * 1000) + +#define AR0521_NATIVE_WIDTH 2604u +#define AR0521_NATIVE_HEIGHT 1964u +#define AR0521_MIN_X_ADDR_START 0u +#define AR0521_MIN_Y_ADDR_START 0u +#define AR0521_MAX_X_ADDR_END 2603u +#define AR0521_MAX_Y_ADDR_END 1955u + +#define AR0521_WIDTH_MIN 8u +#define AR0521_WIDTH_MAX 2592u +#define AR0521_HEIGHT_MIN 8u +#define AR0521_HEIGHT_MAX 1944u + +#define AR0521_WIDTH_BLANKING_MIN 572u +#define AR0521_HEIGHT_BLANKING_MIN 38u /* must be even */ +#define AR0521_TOTAL_HEIGHT_MAX 65535u /* max_frame_length_lines */ +#define AR0521_TOTAL_WIDTH_MAX 65532u /* max_line_length_pck */ + +#define AR0521_ANA_GAIN_MIN 0x00 +#define AR0521_ANA_GAIN_MAX 0x3f +#define AR0521_ANA_GAIN_STEP 0x01 +#define AR0521_ANA_GAIN_DEFAULT 0x00 /* AR0521 registers */ #define AR0521_REG_VT_PIX_CLK_DIV 0x0300 @@ -50,6 +63,8 @@ #define AR0521_REG_RESET_RESTART BIT(1) #define AR0521_REG_RESET_INIT BIT(0) +#define AR0521_REG_ANA_GAIN_CODE_GLOBAL 0x3028 + #define AR0521_REG_GREEN1_GAIN 0x3056 #define AR0521_REG_BLUE_GAIN 0x3058 #define AR0521_REG_RED_GAIN 0x305A @@ -75,6 +90,10 @@ static const char * const ar0521_supply_names[] = { "vaa", /* Analog (2.7V) supply */ }; +static const s64 ar0521_link_frequencies[] = { + 184000000, +}; + struct ar0521_ctrls { struct v4l2_ctrl_handler handler; struct { @@ -107,12 +126,14 @@ struct ar0521_dev { struct v4l2_mbus_framefmt fmt; struct ar0521_ctrls ctrls; unsigned int lane_count; - u16 total_width; - u16 total_height; - u16 pll_pre; - u16 pll_mult; - u16 pll_pre2; - u16 pll_mult2; + struct { + u16 pre; + u16 mult; + u16 pre2; + u16 mult2; + u16 vt_pix; + } pll; + bool streaming; }; @@ -137,6 +158,16 @@ static u32 div64_round_up(u64 v, u32 d) return div_u64(v + d - 1, d); } +static int ar0521_code_to_bpp(struct ar0521_dev *sensor) +{ + switch (sensor->fmt.code) { + case MEDIA_BUS_FMT_SGRBG8_1X8: + return 8; + } + + return -EINVAL; +} + /* Data must be BE16, the first value is the register address */ static int ar0521_write_regs(struct ar0521_dev *sensor, const __be16 *data, unsigned int count) @@ -169,13 +200,17 @@ static int ar0521_write_reg(struct ar0521_dev *sensor, u16 reg, u16 val) static int ar0521_set_geometry(struct ar0521_dev *sensor) { + /* Center the image in the visible output window. */ + u16 x = clamp((AR0521_WIDTH_MAX - sensor->fmt.width) / 2, + AR0521_MIN_X_ADDR_START, AR0521_MAX_X_ADDR_END); + u16 y = clamp(((AR0521_HEIGHT_MAX - sensor->fmt.height) / 2) & ~1, + AR0521_MIN_Y_ADDR_START, AR0521_MAX_Y_ADDR_END); + /* All dimensions are unsigned 12-bit integers */ - u16 x = (AR0521_WIDTH_MAX - sensor->fmt.width) / 2; - u16 y = ((AR0521_HEIGHT_MAX - sensor->fmt.height) / 2) & ~1; __be16 regs[] = { be(AR0521_REG_FRAME_LENGTH_LINES), - be(sensor->total_height), - be(sensor->total_width), + be(sensor->fmt.height + sensor->ctrls.vblank->val), + be(sensor->fmt.width + sensor->ctrls.hblank->val), be(x), be(y), be(x + sensor->fmt.width - 1), @@ -208,8 +243,7 @@ static int ar0521_set_gains(struct ar0521_dev *sensor) return ar0521_write_regs(sensor, regs, ARRAY_SIZE(regs)); } -static u32 calc_pll(struct ar0521_dev *sensor, int num, u32 freq, u16 *pre_ptr, - u16 *mult_ptr) +static u32 calc_pll(struct ar0521_dev *sensor, u32 freq, u16 *pre_ptr, u16 *mult_ptr) { u16 pre = 1, mult = 1, new_pre; u32 pll = AR0521_PLL_MAX + 1; @@ -244,69 +278,84 @@ static u32 calc_pll(struct ar0521_dev *sensor, int num, u32 freq, u16 *pre_ptr, return pll; } -#define DIV 4 -static void ar0521_calc_mode(struct ar0521_dev *sensor) +static void ar0521_calc_pll(struct ar0521_dev *sensor) { - unsigned int speed_mod = 4 / sensor->lane_count; /* 1 with 4 DDR lanes */ - u16 total_width = max(sensor->fmt.width + AR0521_WIDTH_BLANKING_MIN, - AR0521_TOTAL_WIDTH_MIN); - u16 total_height = sensor->fmt.height + AR0521_HEIGHT_BLANKING_MIN; - - /* Calculate approximate pixel clock first */ - u64 pix_clk = AR0521_PIXEL_CLOCK_RATE; - - /* PLL1 drives pixel clock - dual rate */ - pix_clk = calc_pll(sensor, 1, pix_clk * (DIV / 2), &sensor->pll_pre, - &sensor->pll_mult); - pix_clk = div64_round(pix_clk, (DIV / 2)); - calc_pll(sensor, 2, pix_clk * (DIV / 2) * speed_mod, &sensor->pll_pre2, - &sensor->pll_mult2); - - sensor->total_width = total_width; - sensor->total_height = total_height; + unsigned int pixel_clock; + u16 pre, mult; + u32 vco; + int bpp; + + /* + * PLL1 and PLL2 are computed equally even if the application note + * suggests a slower PLL1 clock. Maintain pll1 and pll2 divider and + * multiplier separated to later specialize the calculation procedure. + * + * PLL1: + * - mclk -> / pre_div1 * pre_mul1 = VCO1 = COUNTER_CLOCK + * + * PLL2: + * - mclk -> / pre_div * pre_mul = VCO + * + * VCO -> / vt_pix = PIXEL_CLOCK + * VCO -> / vt_pix / 2 = WORD_CLOCK + * VCO -> / op_sys = SERIAL_CLOCK + * + * With: + * - vt_pix = bpp / 2 + * - WORD_CLOCK = PIXEL_CLOCK / 2 + * - SERIAL_CLOCK = MIPI data rate (Mbps / lane) = WORD_CLOCK * bpp + * NOTE: this implies the MIPI clock is divided internally by 2 + * to account for DDR. + * + * As op_sys_div is fixed to 1: + * + * SERIAL_CLOCK = VCO + * VCO = 2 * MIPI_CLK + * VCO = PIXEL_CLOCK * bpp / 2 + * + * In the clock tree: + * MIPI_CLK = PIXEL_CLOCK * bpp / 2 / 2 + * + * Generic pixel_rate to bus clock frequencey equation: + * MIPI_CLK = V4L2_CID_PIXEL_RATE * bpp / lanes / 2 + * + * From which we derive the PIXEL_CLOCK to use in the clock tree: + * PIXEL_CLOCK = V4L2_CID_PIXEL_RATE * 2 / lanes + * + * Documented clock ranges: + * WORD_CLOCK = (35MHz - 120 MHz) + * PIXEL_CLOCK = (84MHz - 207MHz) + * VCO = (320MHz - 1280MHz) + * + * TODO: in case we have less data lanes we have to reduce the desired + * VCO not to exceed the limits specified by the datasheet and + * consequentially reduce the obtained pixel clock. + */ + pixel_clock = AR0521_PIXEL_CLOCK_RATE * 2 / sensor->lane_count; + bpp = ar0521_code_to_bpp(sensor); + sensor->pll.vt_pix = bpp / 2; + vco = pixel_clock * sensor->pll.vt_pix; + + calc_pll(sensor, vco, &pre, &mult); + + sensor->pll.pre = sensor->pll.pre2 = pre; + sensor->pll.mult = sensor->pll.mult2 = mult; } -static int ar0521_write_mode(struct ar0521_dev *sensor) +static int ar0521_pll_config(struct ar0521_dev *sensor) { __be16 pll_regs[] = { be(AR0521_REG_VT_PIX_CLK_DIV), - /* 0x300 */ be(4), /* vt_pix_clk_div = number of bits / 2 */ + /* 0x300 */ be(sensor->pll.vt_pix), /* vt_pix_clk_div = bpp / 2 */ /* 0x302 */ be(1), /* vt_sys_clk_div */ - /* 0x304 */ be((sensor->pll_pre2 << 8) | sensor->pll_pre), - /* 0x306 */ be((sensor->pll_mult2 << 8) | sensor->pll_mult), - /* 0x308 */ be(8), /* op_pix_clk_div = 2 * vt_pix_clk_div */ + /* 0x304 */ be((sensor->pll.pre2 << 8) | sensor->pll.pre), + /* 0x306 */ be((sensor->pll.mult2 << 8) | sensor->pll.mult), + /* 0x308 */ be(sensor->pll.vt_pix * 2), /* op_pix_clk_div = 2 * vt_pix_clk_div */ /* 0x30A */ be(1) /* op_sys_clk_div */ }; - int ret; - - /* Stop streaming for just a moment */ - ret = ar0521_write_reg(sensor, AR0521_REG_RESET, - AR0521_REG_RESET_DEFAULTS); - if (ret) - return ret; - ret = ar0521_set_geometry(sensor); - if (ret) - return ret; - - ret = ar0521_write_regs(sensor, pll_regs, ARRAY_SIZE(pll_regs)); - if (ret) - return ret; - - ret = ar0521_write_reg(sensor, AR0521_REG_COARSE_INTEGRATION_TIME, - sensor->ctrls.exposure->val); - if (ret) - return ret; - - ret = ar0521_write_reg(sensor, AR0521_REG_RESET, - AR0521_REG_RESET_DEFAULTS | - AR0521_REG_RESET_STREAM); - if (ret) - return ret; - - ret = ar0521_write_reg(sensor, AR0521_REG_TEST_PATTERN_MODE, - sensor->ctrls.test_pattern->val); - return ret; + ar0521_calc_pll(sensor); + return ar0521_write_regs(sensor, pll_regs, ARRAY_SIZE(pll_regs)); } static int ar0521_set_stream(struct ar0521_dev *sensor, bool on) @@ -318,12 +367,21 @@ static int ar0521_set_stream(struct ar0521_dev *sensor, bool on) if (ret < 0) return ret; - ar0521_calc_mode(sensor); - ret = ar0521_write_mode(sensor); + /* Stop streaming for just a moment */ + ret = ar0521_write_reg(sensor, AR0521_REG_RESET, + AR0521_REG_RESET_DEFAULTS); + if (ret) + return ret; + + ret = ar0521_set_geometry(sensor); + if (ret) + return ret; + + ret = ar0521_pll_config(sensor); if (ret) goto err; - ret = ar0521_set_gains(sensor); + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); if (ret) goto err; @@ -406,6 +464,8 @@ static int ar0521_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_format *format) { struct ar0521_dev *sensor = to_ar0521_dev(sd); + int max_vblank, max_hblank, exposure_max; + int ret; ar0521_adj_fmt(&format->format); @@ -416,33 +476,73 @@ static int ar0521_set_fmt(struct v4l2_subdev *sd, fmt = v4l2_subdev_get_try_format(sd, sd_state, 0 /* pad */); *fmt = format->format; - } else { - sensor->fmt = format->format; - ar0521_calc_mode(sensor); + + mutex_unlock(&sensor->lock); + + return 0; } + sensor->fmt = format->format; + ar0521_calc_pll(sensor); + + /* + * Update the exposure and blankings limits. Blankings are also reset + * to the minimum. + */ + max_hblank = AR0521_TOTAL_WIDTH_MAX - sensor->fmt.width; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.hblank, + sensor->ctrls.hblank->minimum, + max_hblank, sensor->ctrls.hblank->step, + sensor->ctrls.hblank->minimum); + if (ret) + goto unlock; + + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.hblank, + sensor->ctrls.hblank->minimum); + if (ret) + goto unlock; + + max_vblank = AR0521_TOTAL_HEIGHT_MAX - sensor->fmt.height; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.vblank, + sensor->ctrls.vblank->minimum, + max_vblank, sensor->ctrls.vblank->step, + sensor->ctrls.vblank->minimum); + if (ret) + goto unlock; + + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.vblank, + sensor->ctrls.vblank->minimum); + if (ret) + goto unlock; + + exposure_max = sensor->fmt.height + AR0521_HEIGHT_BLANKING_MIN - 4; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.exposure, + sensor->ctrls.exposure->minimum, + exposure_max, + sensor->ctrls.exposure->step, + sensor->ctrls.exposure->default_value); +unlock: mutex_unlock(&sensor->lock); - return 0; + + return ret; } static int ar0521_s_ctrl(struct v4l2_ctrl *ctrl) { struct v4l2_subdev *sd = ctrl_to_sd(ctrl); struct ar0521_dev *sensor = to_ar0521_dev(sd); + int exp_max; int ret; /* v4l2_ctrl_lock() locks our own mutex */ switch (ctrl->id) { - case V4L2_CID_HBLANK: case V4L2_CID_VBLANK: - sensor->total_width = sensor->fmt.width + - sensor->ctrls.hblank->val; - sensor->total_height = sensor->fmt.width + - sensor->ctrls.vblank->val; - break; - default: - ret = -EINVAL; + exp_max = sensor->fmt.height + ctrl->val - 4; + __v4l2_ctrl_modify_range(sensor->ctrls.exposure, + sensor->ctrls.exposure->minimum, + exp_max, sensor->ctrls.exposure->step, + sensor->ctrls.exposure->default_value); break; } @@ -455,6 +555,10 @@ static int ar0521_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_VBLANK: ret = ar0521_set_geometry(sensor); break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ar0521_write_reg(sensor, AR0521_REG_ANA_GAIN_CODE_GLOBAL, + ctrl->val); + break; case V4L2_CID_GAIN: case V4L2_CID_RED_BALANCE: case V4L2_CID_BLUE_BALANCE: @@ -469,6 +573,11 @@ static int ar0521_s_ctrl(struct v4l2_ctrl *ctrl) ret = ar0521_write_reg(sensor, AR0521_REG_TEST_PATTERN_MODE, ctrl->val); break; + default: + dev_err(&sensor->i2c_client->dev, + "Unsupported control %x\n", ctrl->id); + ret = -EINVAL; + break; } pm_runtime_put(&sensor->i2c_client->dev); @@ -491,6 +600,8 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) const struct v4l2_ctrl_ops *ops = &ar0521_ctrl_ops; struct ar0521_ctrls *ctrls = &sensor->ctrls; struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int max_vblank, max_hblank, exposure_max; + struct v4l2_ctrl *link_freq; int ret; v4l2_ctrl_handler_init(hdl, 32); @@ -498,6 +609,11 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) /* We can use our own mutex for the ctrl lock */ hdl->lock = &sensor->lock; + /* Analog gain */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, + AR0521_ANA_GAIN_MIN, AR0521_ANA_GAIN_MAX, + AR0521_ANA_GAIN_STEP, AR0521_ANA_GAIN_DEFAULT); + /* Manual gain */ ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, 0, 511, 1, 0); ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, @@ -506,11 +622,17 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) -512, 511, 1, 0); v4l2_ctrl_cluster(3, &ctrls->gain); + /* Initialize blanking limits using the default 2592x1944 format. */ + max_hblank = AR0521_TOTAL_WIDTH_MAX - AR0521_WIDTH_MAX; ctrls->hblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, - AR0521_WIDTH_BLANKING_MIN, 4094, 1, + AR0521_WIDTH_BLANKING_MIN, + max_hblank, 1, AR0521_WIDTH_BLANKING_MIN); + + max_vblank = AR0521_TOTAL_HEIGHT_MAX - AR0521_HEIGHT_MAX; ctrls->vblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, - AR0521_HEIGHT_BLANKING_MIN, 4094, 2, + AR0521_HEIGHT_BLANKING_MIN, + max_vblank, 2, AR0521_HEIGHT_BLANKING_MIN); v4l2_ctrl_cluster(2, &ctrls->hblank); @@ -520,9 +642,16 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) AR0521_PIXEL_CLOCK_MAX, 1, AR0521_PIXEL_CLOCK_RATE); - /* Manual exposure time */ + /* Manual exposure time: max exposure time = visible + blank - 4 */ + exposure_max = AR0521_HEIGHT_MAX + AR0521_HEIGHT_BLANKING_MIN - 4; ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 0, - 65535, 1, 360); + exposure_max, 1, 0x70); + + link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(ar0521_link_frequencies) - 1, + 0, ar0521_link_frequencies); + if (link_freq) + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ctrls->test_pattern = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, @@ -799,6 +928,24 @@ static int ar0521_enum_mbus_code(struct v4l2_subdev *sd, return 0; } +static int ar0521_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG8_1X8) + return -EINVAL; + + fse->min_width = AR0521_WIDTH_MIN; + fse->max_width = AR0521_WIDTH_MAX; + fse->min_height = AR0521_HEIGHT_MIN; + fse->max_height = AR0521_HEIGHT_MAX; + + return 0; +} + static int ar0521_pre_streamon(struct v4l2_subdev *sd, u32 flags) { struct ar0521_dev *sensor = to_ar0521_dev(sd); @@ -865,6 +1012,7 @@ static const struct v4l2_subdev_video_ops ar0521_video_ops = { static const struct v4l2_subdev_pad_ops ar0521_pad_ops = { .enum_mbus_code = ar0521_enum_mbus_code, + .enum_frame_size = ar0521_enum_frame_size, .get_fmt = ar0521_get_fmt, .set_fmt = ar0521_set_fmt, }; diff --git a/drivers/media/i2c/bt819.c b/drivers/media/i2c/bt819.c index 4d9bb6eb7d65..39f8a5361166 100644 --- a/drivers/media/i2c/bt819.c +++ b/drivers/media/i2c/bt819.c @@ -380,8 +380,7 @@ static const struct v4l2_subdev_ops bt819_ops = { /* ----------------------------------------------------------------------- */ -static int bt819_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int bt819_probe(struct i2c_client *client) { int i, ver; struct bt819 *decoder; @@ -469,7 +468,7 @@ static struct i2c_driver bt819_driver = { .driver = { .name = "bt819", }, - .probe = bt819_probe, + .probe_new = bt819_probe, .remove = bt819_remove, .id_table = bt819_id, }; diff --git a/drivers/media/i2c/bt856.c b/drivers/media/i2c/bt856.c index 70443ef1ac46..d1d397b15b85 100644 --- a/drivers/media/i2c/bt856.c +++ b/drivers/media/i2c/bt856.c @@ -181,8 +181,7 @@ static const struct v4l2_subdev_ops bt856_ops = { /* ----------------------------------------------------------------------- */ -static int bt856_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int bt856_probe(struct i2c_client *client) { struct bt856 *encoder; struct v4l2_subdev *sd; @@ -240,7 +239,7 @@ static struct i2c_driver bt856_driver = { .driver = { .name = "bt856", }, - .probe = bt856_probe, + .probe_new = bt856_probe, .remove = bt856_remove, .id_table = bt856_id, }; diff --git a/drivers/media/i2c/bt866.c b/drivers/media/i2c/bt866.c index c2508cbafd02..d632d9a07f04 100644 --- a/drivers/media/i2c/bt866.c +++ b/drivers/media/i2c/bt866.c @@ -173,8 +173,7 @@ static const struct v4l2_subdev_ops bt866_ops = { .video = &bt866_video_ops, }; -static int bt866_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int bt866_probe(struct i2c_client *client) { struct bt866 *encoder; struct v4l2_subdev *sd; @@ -207,7 +206,7 @@ static struct i2c_driver bt866_driver = { .driver = { .name = "bt866", }, - .probe = bt866_probe, + .probe_new = bt866_probe, .remove = bt866_remove, .id_table = bt866_id, }; diff --git a/drivers/media/i2c/cs3308.c b/drivers/media/i2c/cs3308.c index d901a59883a9..a0b66c04fe25 100644 --- a/drivers/media/i2c/cs3308.c +++ b/drivers/media/i2c/cs3308.c @@ -64,8 +64,7 @@ static const struct v4l2_subdev_ops cs3308_ops = { /* ----------------------------------------------------------------------- */ -static int cs3308_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cs3308_probe(struct i2c_client *client) { struct v4l2_subdev *sd; unsigned i; @@ -119,7 +118,7 @@ static struct i2c_driver cs3308_driver = { .driver = { .name = "cs3308", }, - .probe = cs3308_probe, + .probe_new = cs3308_probe, .remove = cs3308_remove, .id_table = cs3308_id, }; diff --git a/drivers/media/i2c/cs5345.c b/drivers/media/i2c/cs5345.c index 591b1e7b24ee..ac4b5632fc46 100644 --- a/drivers/media/i2c/cs5345.c +++ b/drivers/media/i2c/cs5345.c @@ -136,8 +136,7 @@ static const struct v4l2_subdev_ops cs5345_ops = { /* ----------------------------------------------------------------------- */ -static int cs5345_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cs5345_probe(struct i2c_client *client) { struct cs5345_state *state; struct v4l2_subdev *sd; @@ -199,7 +198,7 @@ static struct i2c_driver cs5345_driver = { .driver = { .name = "cs5345", }, - .probe = cs5345_probe, + .probe_new = cs5345_probe, .remove = cs5345_remove, .id_table = cs5345_id, }; diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c index f1a978af82ef..46cf422270b2 100644 --- a/drivers/media/i2c/cx25840/cx25840-core.c +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -5825,8 +5825,7 @@ static u32 get_cx2388x_ident(struct i2c_client *client) return ret; } -static int cx25840_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int cx25840_probe(struct i2c_client *client) { struct cx25840_state *state; struct v4l2_subdev *sd; @@ -6046,7 +6045,7 @@ static struct i2c_driver cx25840_driver = { .driver = { .name = "cx25840", }, - .probe = cx25840_probe, + .probe_new = cx25840_probe, .remove = cx25840_remove, .id_table = cx25840_id, }; diff --git a/drivers/media/i2c/dw9768.c b/drivers/media/i2c/dw9768.c index 0f47ef015a1d..83a3ee275bbe 100644 --- a/drivers/media/i2c/dw9768.c +++ b/drivers/media/i2c/dw9768.c @@ -414,6 +414,7 @@ static int dw9768_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct dw9768 *dw9768; + bool full_power; unsigned int i; int ret; @@ -469,13 +470,23 @@ static int dw9768_probe(struct i2c_client *client) dw9768->sd.entity.function = MEDIA_ENT_F_LENS; + /* + * Figure out whether we're going to power up the device here. Generally + * this is done if CONFIG_PM is disabled in a DT system or the device is + * to be powered on in an ACPI system. Similarly for power off in + * remove. + */ pm_runtime_enable(dev); - if (!pm_runtime_enabled(dev)) { + full_power = (is_acpi_node(dev_fwnode(dev)) && + acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev)); + if (full_power) { ret = dw9768_runtime_resume(dev); if (ret < 0) { dev_err(dev, "failed to power on: %d\n", ret); goto err_clean_entity; } + pm_runtime_set_active(dev); } ret = v4l2_async_register_subdev(&dw9768->sd); @@ -484,14 +495,17 @@ static int dw9768_probe(struct i2c_client *client) goto err_power_off; } + pm_runtime_idle(dev); + return 0; err_power_off: - if (pm_runtime_enabled(dev)) - pm_runtime_disable(dev); - else + if (full_power) { dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } err_clean_entity: + pm_runtime_disable(dev); media_entity_cleanup(&dw9768->sd.entity); err_free_handler: v4l2_ctrl_handler_free(&dw9768->ctrls); @@ -503,14 +517,17 @@ static void dw9768_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9768 *dw9768 = sd_to_dw9768(sd); + struct device *dev = &client->dev; v4l2_async_unregister_subdev(&dw9768->sd); v4l2_ctrl_handler_free(&dw9768->ctrls); media_entity_cleanup(&dw9768->sd.entity); - pm_runtime_disable(&client->dev); - if (!pm_runtime_status_suspended(&client->dev)) - dw9768_runtime_suspend(&client->dev); - pm_runtime_set_suspended(&client->dev); + if ((is_acpi_node(dev_fwnode(dev)) && acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev))) { + dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } + pm_runtime_disable(dev); } static const struct of_device_id dw9768_of_table[] = { diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c index c5b69823f257..7c61873b7198 100644 --- a/drivers/media/i2c/hi846.c +++ b/drivers/media/i2c/hi846.c @@ -2008,22 +2008,24 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { dev_err(dev, "number of CSI2 data lanes %d is not supported", bus_cfg.bus.mipi_csi2.num_data_lanes); - v4l2_fwnode_endpoint_free(&bus_cfg); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } hi846->nr_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; if (!bus_cfg.nr_of_link_frequencies) { dev_err(dev, "link-frequency property not found in DT\n"); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } /* Check that link frequences for all the modes are in device tree */ fq = hi846_check_link_freqs(hi846, &bus_cfg); if (fq) { dev_err(dev, "Link frequency of %lld is not supported\n", fq); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } v4l2_fwnode_endpoint_free(&bus_cfg); @@ -2044,6 +2046,10 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) } return 0; + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; } static int hi846_probe(struct i2c_client *client) diff --git a/drivers/media/i2c/imx208.c b/drivers/media/i2c/imx208.c index a0e17bb9d4ca..64c70ebf9869 100644 --- a/drivers/media/i2c/imx208.c +++ b/drivers/media/i2c/imx208.c @@ -937,8 +937,12 @@ static int imx208_init_controls(struct imx208 *imx208) imx208->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx208->hflip) + imx208->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; imx208->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx208->vflip) + imx208->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX208_ANA_GAIN_MIN, IMX208_ANA_GAIN_MAX, diff --git a/drivers/media/i2c/imx274.c b/drivers/media/i2c/imx274.c index a00761b1e18c..9219f3c9594b 100644 --- a/drivers/media/i2c/imx274.c +++ b/drivers/media/i2c/imx274.c @@ -2060,9 +2060,8 @@ static int imx274_probe(struct i2c_client *client) imx274->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(imx274->reset_gpio)) { - if (PTR_ERR(imx274->reset_gpio) != -EPROBE_DEFER) - dev_err(dev, "Reset GPIO not setup in DT"); - ret = PTR_ERR(imx274->reset_gpio); + ret = dev_err_probe(dev, PTR_ERR(imx274->reset_gpio), + "Reset GPIO not setup in DT\n"); goto err_me; } diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 1ce64dcdf7f0..218ded13fd80 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -22,22 +22,135 @@ #include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> -#define IMX290_STANDBY 0x3000 -#define IMX290_REGHOLD 0x3001 -#define IMX290_XMSTA 0x3002 -#define IMX290_FR_FDG_SEL 0x3009 -#define IMX290_BLKLEVEL_LOW 0x300a -#define IMX290_BLKLEVEL_HIGH 0x300b -#define IMX290_GAIN 0x3014 -#define IMX290_HMAX_LOW 0x301c -#define IMX290_HMAX_HIGH 0x301d -#define IMX290_PGCTRL 0x308c -#define IMX290_PHY_LANE_NUM 0x3407 -#define IMX290_CSI_LANE_MODE 0x3443 - -#define IMX290_PGCTRL_REGEN BIT(0) -#define IMX290_PGCTRL_THRU BIT(1) -#define IMX290_PGCTRL_MODE(n) ((n) << 4) +#define IMX290_REG_SIZE_SHIFT 16 +#define IMX290_REG_ADDR_MASK 0xffff +#define IMX290_REG_8BIT(n) ((1U << IMX290_REG_SIZE_SHIFT) | (n)) +#define IMX290_REG_16BIT(n) ((2U << IMX290_REG_SIZE_SHIFT) | (n)) +#define IMX290_REG_24BIT(n) ((3U << IMX290_REG_SIZE_SHIFT) | (n)) + +#define IMX290_STANDBY IMX290_REG_8BIT(0x3000) +#define IMX290_REGHOLD IMX290_REG_8BIT(0x3001) +#define IMX290_XMSTA IMX290_REG_8BIT(0x3002) +#define IMX290_ADBIT IMX290_REG_8BIT(0x3005) +#define IMX290_ADBIT_10BIT (0 << 0) +#define IMX290_ADBIT_12BIT (1 << 0) +#define IMX290_CTRL_07 IMX290_REG_8BIT(0x3007) +#define IMX290_VREVERSE BIT(0) +#define IMX290_HREVERSE BIT(1) +#define IMX290_WINMODE_1080P (0 << 4) +#define IMX290_WINMODE_720P (1 << 4) +#define IMX290_WINMODE_CROP (4 << 4) +#define IMX290_FR_FDG_SEL IMX290_REG_8BIT(0x3009) +#define IMX290_BLKLEVEL IMX290_REG_16BIT(0x300a) +#define IMX290_GAIN IMX290_REG_8BIT(0x3014) +#define IMX290_VMAX IMX290_REG_24BIT(0x3018) +#define IMX290_HMAX IMX290_REG_16BIT(0x301c) +#define IMX290_SHS1 IMX290_REG_24BIT(0x3020) +#define IMX290_WINWV_OB IMX290_REG_8BIT(0x303a) +#define IMX290_WINPV IMX290_REG_16BIT(0x303c) +#define IMX290_WINWV IMX290_REG_16BIT(0x303e) +#define IMX290_WINPH IMX290_REG_16BIT(0x3040) +#define IMX290_WINWH IMX290_REG_16BIT(0x3042) +#define IMX290_OUT_CTRL IMX290_REG_8BIT(0x3046) +#define IMX290_ODBIT_10BIT (0 << 0) +#define IMX290_ODBIT_12BIT (1 << 0) +#define IMX290_OPORTSEL_PARALLEL (0x0 << 4) +#define IMX290_OPORTSEL_LVDS_2CH (0xd << 4) +#define IMX290_OPORTSEL_LVDS_4CH (0xe << 4) +#define IMX290_OPORTSEL_LVDS_8CH (0xf << 4) +#define IMX290_XSOUTSEL IMX290_REG_8BIT(0x304b) +#define IMX290_XSOUTSEL_XVSOUTSEL_HIGH (0 << 0) +#define IMX290_XSOUTSEL_XVSOUTSEL_VSYNC (2 << 0) +#define IMX290_XSOUTSEL_XHSOUTSEL_HIGH (0 << 2) +#define IMX290_XSOUTSEL_XHSOUTSEL_HSYNC (2 << 2) +#define IMX290_INCKSEL1 IMX290_REG_8BIT(0x305c) +#define IMX290_INCKSEL2 IMX290_REG_8BIT(0x305d) +#define IMX290_INCKSEL3 IMX290_REG_8BIT(0x305e) +#define IMX290_INCKSEL4 IMX290_REG_8BIT(0x305f) +#define IMX290_PGCTRL IMX290_REG_8BIT(0x308c) +#define IMX290_ADBIT1 IMX290_REG_8BIT(0x3129) +#define IMX290_ADBIT1_10BIT 0x1d +#define IMX290_ADBIT1_12BIT 0x00 +#define IMX290_INCKSEL5 IMX290_REG_8BIT(0x315e) +#define IMX290_INCKSEL6 IMX290_REG_8BIT(0x3164) +#define IMX290_ADBIT2 IMX290_REG_8BIT(0x317c) +#define IMX290_ADBIT2_10BIT 0x12 +#define IMX290_ADBIT2_12BIT 0x00 +#define IMX290_CHIP_ID IMX290_REG_16BIT(0x319a) +#define IMX290_ADBIT3 IMX290_REG_8BIT(0x31ec) +#define IMX290_ADBIT3_10BIT 0x37 +#define IMX290_ADBIT3_12BIT 0x0e +#define IMX290_REPETITION IMX290_REG_8BIT(0x3405) +#define IMX290_PHY_LANE_NUM IMX290_REG_8BIT(0x3407) +#define IMX290_OPB_SIZE_V IMX290_REG_8BIT(0x3414) +#define IMX290_Y_OUT_SIZE IMX290_REG_16BIT(0x3418) +#define IMX290_CSI_DT_FMT IMX290_REG_16BIT(0x3441) +#define IMX290_CSI_DT_FMT_RAW10 0x0a0a +#define IMX290_CSI_DT_FMT_RAW12 0x0c0c +#define IMX290_CSI_LANE_MODE IMX290_REG_8BIT(0x3443) +#define IMX290_EXTCK_FREQ IMX290_REG_16BIT(0x3444) +#define IMX290_TCLKPOST IMX290_REG_16BIT(0x3446) +#define IMX290_THSZERO IMX290_REG_16BIT(0x3448) +#define IMX290_THSPREPARE IMX290_REG_16BIT(0x344a) +#define IMX290_TCLKTRAIL IMX290_REG_16BIT(0x344c) +#define IMX290_THSTRAIL IMX290_REG_16BIT(0x344e) +#define IMX290_TCLKZERO IMX290_REG_16BIT(0x3450) +#define IMX290_TCLKPREPARE IMX290_REG_16BIT(0x3452) +#define IMX290_TLPX IMX290_REG_16BIT(0x3454) +#define IMX290_X_OUT_SIZE IMX290_REG_16BIT(0x3472) + +#define IMX290_PGCTRL_REGEN BIT(0) +#define IMX290_PGCTRL_THRU BIT(1) +#define IMX290_PGCTRL_MODE(n) ((n) << 4) + +#define IMX290_VMAX_DEFAULT 1125 + + +/* + * The IMX290 pixel array is organized as follows: + * + * +------------------------------------+ + * | Optical Black | } Vertical effective optical black (10) + * +---+------------------------------------+---+ + * | | | | } Effective top margin (8) + * | | +----------------------------+ | | \ + * | | | | | | | + * | | | | | | | + * | | | | | | | + * | | | Recording Pixel Area | | | | Recommended height (1080) + * | | | | | | | + * | | | | | | | + * | | | | | | | + * | | +----------------------------+ | | / + * | | | | } Effective bottom margin (9) + * +---+------------------------------------+---+ + * <-> <-> <--------------------------> <-> <-> + * \---- Ignored right margin (4) + * \-------- Effective right margin (9) + * \------------------------- Recommended width (1920) + * \----------------------------------------- Effective left margin (8) + * \--------------------------------------------- Ignored left margin (4) + * + * The optical black lines are output over CSI-2 with a separate data type. + * + * The pixel array is meant to have 1920x1080 usable pixels after image + * processing in an ISP. It has 8 (9) extra active pixels usable for color + * processing in the ISP on the top and left (bottom and right) sides of the + * image. In addition, 4 additional pixels are present on the left and right + * sides of the image, documented as "ignored area". + * + * As far as is understood, all pixels of the pixel array (ignored area, color + * processing margins and recording area) can be output by the sensor. + */ + +#define IMX290_PIXEL_ARRAY_WIDTH 1945 +#define IMX290_PIXEL_ARRAY_HEIGHT 1097 +#define IMX920_PIXEL_ARRAY_MARGIN_LEFT 12 +#define IMX920_PIXEL_ARRAY_MARGIN_RIGHT 13 +#define IMX920_PIXEL_ARRAY_MARGIN_TOP 8 +#define IMX920_PIXEL_ARRAY_MARGIN_BOTTOM 9 +#define IMX290_PIXEL_ARRAY_RECORDING_WIDTH 1920 +#define IMX290_PIXEL_ARRAY_RECORDING_HEIGHT 1080 static const char * const imx290_supply_name[] = { "vdda", @@ -48,8 +161,8 @@ static const char * const imx290_supply_name[] = { #define IMX290_NUM_SUPPLIES ARRAY_SIZE(imx290_supply_name) struct imx290_regval { - u16 reg; - u8 val; + u32 reg; + u32 val; }; struct imx290_mode { @@ -80,6 +193,8 @@ struct imx290 { struct v4l2_ctrl_handler ctrls; struct v4l2_ctrl *link_freq; struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; struct mutex lock; }; @@ -97,7 +212,6 @@ static const struct imx290_pixfmt imx290_formats[] = { static const struct regmap_config imx290_regmap_config = { .reg_bits = 16, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, }; static const char * const imx290_test_pattern_menu[] = { @@ -112,163 +226,129 @@ static const char * const imx290_test_pattern_menu[] = { }; static const struct imx290_regval imx290_global_init_settings[] = { - { 0x3007, 0x00 }, - { 0x3018, 0x65 }, - { 0x3019, 0x04 }, - { 0x301a, 0x00 }, - { 0x3444, 0x20 }, - { 0x3445, 0x25 }, - { 0x303a, 0x0c }, - { 0x3040, 0x00 }, - { 0x3041, 0x00 }, - { 0x303c, 0x00 }, - { 0x303d, 0x00 }, - { 0x3042, 0x9c }, - { 0x3043, 0x07 }, - { 0x303e, 0x49 }, - { 0x303f, 0x04 }, - { 0x304b, 0x0a }, - { 0x300f, 0x00 }, - { 0x3010, 0x21 }, - { 0x3012, 0x64 }, - { 0x3016, 0x09 }, - { 0x3070, 0x02 }, - { 0x3071, 0x11 }, - { 0x309b, 0x10 }, - { 0x309c, 0x22 }, - { 0x30a2, 0x02 }, - { 0x30a6, 0x20 }, - { 0x30a8, 0x20 }, - { 0x30aa, 0x20 }, - { 0x30ac, 0x20 }, - { 0x30b0, 0x43 }, - { 0x3119, 0x9e }, - { 0x311c, 0x1e }, - { 0x311e, 0x08 }, - { 0x3128, 0x05 }, - { 0x313d, 0x83 }, - { 0x3150, 0x03 }, - { 0x317e, 0x00 }, - { 0x32b8, 0x50 }, - { 0x32b9, 0x10 }, - { 0x32ba, 0x00 }, - { 0x32bb, 0x04 }, - { 0x32c8, 0x50 }, - { 0x32c9, 0x10 }, - { 0x32ca, 0x00 }, - { 0x32cb, 0x04 }, - { 0x332c, 0xd3 }, - { 0x332d, 0x10 }, - { 0x332e, 0x0d }, - { 0x3358, 0x06 }, - { 0x3359, 0xe1 }, - { 0x335a, 0x11 }, - { 0x3360, 0x1e }, - { 0x3361, 0x61 }, - { 0x3362, 0x10 }, - { 0x33b0, 0x50 }, - { 0x33b2, 0x1a }, - { 0x33b3, 0x04 }, + { IMX290_CTRL_07, IMX290_WINMODE_1080P }, + { IMX290_VMAX, IMX290_VMAX_DEFAULT }, + { IMX290_EXTCK_FREQ, 0x2520 }, + { IMX290_WINWV_OB, 12 }, + { IMX290_WINPH, 0 }, + { IMX290_WINPV, 0 }, + { IMX290_WINWH, 1948 }, + { IMX290_WINWV, 1097 }, + { IMX290_XSOUTSEL, IMX290_XSOUTSEL_XVSOUTSEL_VSYNC | + IMX290_XSOUTSEL_XHSOUTSEL_HSYNC }, + { IMX290_REG_8BIT(0x300f), 0x00 }, + { IMX290_REG_8BIT(0x3010), 0x21 }, + { IMX290_REG_8BIT(0x3012), 0x64 }, + { IMX290_REG_8BIT(0x3013), 0x00 }, + { IMX290_REG_8BIT(0x3016), 0x09 }, + { IMX290_REG_8BIT(0x3070), 0x02 }, + { IMX290_REG_8BIT(0x3071), 0x11 }, + { IMX290_REG_8BIT(0x309b), 0x10 }, + { IMX290_REG_8BIT(0x309c), 0x22 }, + { IMX290_REG_8BIT(0x30a2), 0x02 }, + { IMX290_REG_8BIT(0x30a6), 0x20 }, + { IMX290_REG_8BIT(0x30a8), 0x20 }, + { IMX290_REG_8BIT(0x30aa), 0x20 }, + { IMX290_REG_8BIT(0x30ac), 0x20 }, + { IMX290_REG_8BIT(0x30b0), 0x43 }, + { IMX290_REG_8BIT(0x3119), 0x9e }, + { IMX290_REG_8BIT(0x311c), 0x1e }, + { IMX290_REG_8BIT(0x311e), 0x08 }, + { IMX290_REG_8BIT(0x3128), 0x05 }, + { IMX290_REG_8BIT(0x313d), 0x83 }, + { IMX290_REG_8BIT(0x3150), 0x03 }, + { IMX290_REG_8BIT(0x317e), 0x00 }, + { IMX290_REG_8BIT(0x32b8), 0x50 }, + { IMX290_REG_8BIT(0x32b9), 0x10 }, + { IMX290_REG_8BIT(0x32ba), 0x00 }, + { IMX290_REG_8BIT(0x32bb), 0x04 }, + { IMX290_REG_8BIT(0x32c8), 0x50 }, + { IMX290_REG_8BIT(0x32c9), 0x10 }, + { IMX290_REG_8BIT(0x32ca), 0x00 }, + { IMX290_REG_8BIT(0x32cb), 0x04 }, + { IMX290_REG_8BIT(0x332c), 0xd3 }, + { IMX290_REG_8BIT(0x332d), 0x10 }, + { IMX290_REG_8BIT(0x332e), 0x0d }, + { IMX290_REG_8BIT(0x3358), 0x06 }, + { IMX290_REG_8BIT(0x3359), 0xe1 }, + { IMX290_REG_8BIT(0x335a), 0x11 }, + { IMX290_REG_8BIT(0x3360), 0x1e }, + { IMX290_REG_8BIT(0x3361), 0x61 }, + { IMX290_REG_8BIT(0x3362), 0x10 }, + { IMX290_REG_8BIT(0x33b0), 0x50 }, + { IMX290_REG_8BIT(0x33b2), 0x1a }, + { IMX290_REG_8BIT(0x33b3), 0x04 }, + { IMX290_REG_8BIT(0x3480), 0x49 }, }; static const struct imx290_regval imx290_1080p_settings[] = { /* mode settings */ - { 0x3007, 0x00 }, - { 0x303a, 0x0c }, - { 0x3414, 0x0a }, - { 0x3472, 0x80 }, - { 0x3473, 0x07 }, - { 0x3418, 0x38 }, - { 0x3419, 0x04 }, - { 0x3012, 0x64 }, - { 0x3013, 0x00 }, - { 0x305c, 0x18 }, - { 0x305d, 0x03 }, - { 0x305e, 0x20 }, - { 0x305f, 0x01 }, - { 0x315e, 0x1a }, - { 0x3164, 0x1a }, - { 0x3480, 0x49 }, + { IMX290_CTRL_07, IMX290_WINMODE_1080P }, + { IMX290_WINWV_OB, 12 }, + { IMX290_OPB_SIZE_V, 10 }, + { IMX290_X_OUT_SIZE, 1920 }, + { IMX290_Y_OUT_SIZE, 1080 }, + { IMX290_INCKSEL1, 0x18 }, + { IMX290_INCKSEL2, 0x03 }, + { IMX290_INCKSEL3, 0x20 }, + { IMX290_INCKSEL4, 0x01 }, + { IMX290_INCKSEL5, 0x1a }, + { IMX290_INCKSEL6, 0x1a }, /* data rate settings */ - { 0x3405, 0x10 }, - { 0x3446, 0x57 }, - { 0x3447, 0x00 }, - { 0x3448, 0x37 }, - { 0x3449, 0x00 }, - { 0x344a, 0x1f }, - { 0x344b, 0x00 }, - { 0x344c, 0x1f }, - { 0x344d, 0x00 }, - { 0x344e, 0x1f }, - { 0x344f, 0x00 }, - { 0x3450, 0x77 }, - { 0x3451, 0x00 }, - { 0x3452, 0x1f }, - { 0x3453, 0x00 }, - { 0x3454, 0x17 }, - { 0x3455, 0x00 }, + { IMX290_REPETITION, 0x10 }, + { IMX290_TCLKPOST, 87 }, + { IMX290_THSZERO, 55 }, + { IMX290_THSPREPARE, 31 }, + { IMX290_TCLKTRAIL, 31 }, + { IMX290_THSTRAIL, 31 }, + { IMX290_TCLKZERO, 119 }, + { IMX290_TCLKPREPARE, 31 }, + { IMX290_TLPX, 23 }, }; static const struct imx290_regval imx290_720p_settings[] = { /* mode settings */ - { 0x3007, 0x10 }, - { 0x303a, 0x06 }, - { 0x3414, 0x04 }, - { 0x3472, 0x00 }, - { 0x3473, 0x05 }, - { 0x3418, 0xd0 }, - { 0x3419, 0x02 }, - { 0x3012, 0x64 }, - { 0x3013, 0x00 }, - { 0x305c, 0x20 }, - { 0x305d, 0x00 }, - { 0x305e, 0x20 }, - { 0x305f, 0x01 }, - { 0x315e, 0x1a }, - { 0x3164, 0x1a }, - { 0x3480, 0x49 }, + { IMX290_CTRL_07, IMX290_WINMODE_720P }, + { IMX290_WINWV_OB, 6 }, + { IMX290_OPB_SIZE_V, 4 }, + { IMX290_X_OUT_SIZE, 1280 }, + { IMX290_Y_OUT_SIZE, 720 }, + { IMX290_INCKSEL1, 0x20 }, + { IMX290_INCKSEL2, 0x00 }, + { IMX290_INCKSEL3, 0x20 }, + { IMX290_INCKSEL4, 0x01 }, + { IMX290_INCKSEL5, 0x1a }, + { IMX290_INCKSEL6, 0x1a }, /* data rate settings */ - { 0x3405, 0x10 }, - { 0x3446, 0x4f }, - { 0x3447, 0x00 }, - { 0x3448, 0x2f }, - { 0x3449, 0x00 }, - { 0x344a, 0x17 }, - { 0x344b, 0x00 }, - { 0x344c, 0x17 }, - { 0x344d, 0x00 }, - { 0x344e, 0x17 }, - { 0x344f, 0x00 }, - { 0x3450, 0x57 }, - { 0x3451, 0x00 }, - { 0x3452, 0x17 }, - { 0x3453, 0x00 }, - { 0x3454, 0x17 }, - { 0x3455, 0x00 }, + { IMX290_REPETITION, 0x10 }, + { IMX290_TCLKPOST, 79 }, + { IMX290_THSZERO, 47 }, + { IMX290_THSPREPARE, 23 }, + { IMX290_TCLKTRAIL, 23 }, + { IMX290_THSTRAIL, 23 }, + { IMX290_TCLKZERO, 87 }, + { IMX290_TCLKPREPARE, 23 }, + { IMX290_TLPX, 23 }, }; static const struct imx290_regval imx290_10bit_settings[] = { - { 0x3005, 0x00}, - { 0x3046, 0x00}, - { 0x3129, 0x1d}, - { 0x317c, 0x12}, - { 0x31ec, 0x37}, - { 0x3441, 0x0a}, - { 0x3442, 0x0a}, - { 0x300a, 0x3c}, - { 0x300b, 0x00}, + { IMX290_ADBIT, IMX290_ADBIT_10BIT }, + { IMX290_OUT_CTRL, IMX290_ODBIT_10BIT }, + { IMX290_ADBIT1, IMX290_ADBIT1_10BIT }, + { IMX290_ADBIT2, IMX290_ADBIT2_10BIT }, + { IMX290_ADBIT3, IMX290_ADBIT3_10BIT }, + { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW10 }, + { IMX290_BLKLEVEL, 60 }, }; static const struct imx290_regval imx290_12bit_settings[] = { - { 0x3005, 0x01 }, - { 0x3046, 0x01 }, - { 0x3129, 0x00 }, - { 0x317c, 0x00 }, - { 0x31ec, 0x0e }, - { 0x3441, 0x0c }, - { 0x3442, 0x0c }, - { 0x300a, 0xf0 }, - { 0x300b, 0x00 }, + { IMX290_ADBIT, IMX290_ADBIT_12BIT }, + { IMX290_OUT_CTRL, IMX290_ODBIT_12BIT }, + { IMX290_ADBIT1, IMX290_ADBIT1_12BIT }, + { IMX290_ADBIT2, IMX290_ADBIT2_12BIT }, + { IMX290_ADBIT3, IMX290_ADBIT3_12BIT }, + { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW12 }, + { IMX290_BLKLEVEL, 240 }, }; /* supported link frequencies */ @@ -308,7 +388,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = { { .width = 1920, .height = 1080, - .hmax = 0x1130, + .hmax = 4400, .link_freq_index = FREQ_INDEX_1080P, .data = imx290_1080p_settings, .data_size = ARRAY_SIZE(imx290_1080p_settings), @@ -316,7 +396,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = { { .width = 1280, .height = 720, - .hmax = 0x19c8, + .hmax = 6600, .link_freq_index = FREQ_INDEX_720P, .data = imx290_720p_settings, .data_size = ARRAY_SIZE(imx290_720p_settings), @@ -327,7 +407,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = { { .width = 1920, .height = 1080, - .hmax = 0x0898, + .hmax = 2200, .link_freq_index = FREQ_INDEX_1080P, .data = imx290_1080p_settings, .data_size = ARRAY_SIZE(imx290_1080p_settings), @@ -335,7 +415,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = { { .width = 1280, .height = 720, - .hmax = 0x0ce4, + .hmax = 3300, .link_freq_index = FREQ_INDEX_720P, .data = imx290_720p_settings, .data_size = ARRAY_SIZE(imx290_720p_settings), @@ -363,30 +443,40 @@ static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd) return container_of(_sd, struct imx290, sd); } -static inline int __always_unused imx290_read_reg(struct imx290 *imx290, u16 addr, u8 *value) +static int __always_unused imx290_read(struct imx290 *imx290, u32 addr, u32 *value) { - unsigned int regval; + u8 data[3] = { 0, 0, 0 }; int ret; - ret = regmap_read(imx290->regmap, addr, ®val); - if (ret) { - dev_err(imx290->dev, "I2C read failed for addr: %x\n", addr); + ret = regmap_raw_read(imx290->regmap, addr & IMX290_REG_ADDR_MASK, + data, (addr >> IMX290_REG_SIZE_SHIFT) & 3); + if (ret < 0) { + dev_err(imx290->dev, "%u-bit read from 0x%04x failed: %d\n", + ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX290_REG_ADDR_MASK, ret); return ret; } - *value = regval & 0xff; - + *value = (data[2] << 16) | (data[1] << 8) | data[0]; return 0; } -static int imx290_write_reg(struct imx290 *imx290, u16 addr, u8 value) +static int imx290_write(struct imx290 *imx290, u32 addr, u32 value, int *err) { + u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 }; int ret; - ret = regmap_write(imx290->regmap, addr, value); - if (ret) { - dev_err(imx290->dev, "I2C write failed for addr: %x\n", addr); - return ret; + if (err && *err) + return *err; + + ret = regmap_raw_write(imx290->regmap, addr & IMX290_REG_ADDR_MASK, + data, (addr >> IMX290_REG_SIZE_SHIFT) & 3); + if (ret < 0) { + dev_err(imx290->dev, "%u-bit write to 0x%04x failed: %d\n", + ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX290_REG_ADDR_MASK, ret); + if (err) + *err = ret; } return ret; @@ -400,7 +490,7 @@ static int imx290_set_register_array(struct imx290 *imx290, int ret; for (i = 0; i < num_settings; ++i, ++settings) { - ret = imx290_write_reg(imx290, settings->reg, settings->val); + ret = imx290_write(imx290, settings->reg, settings->val, NULL); if (ret < 0) return ret; } @@ -411,59 +501,16 @@ static int imx290_set_register_array(struct imx290 *imx290, return 0; } -static int imx290_write_buffered_reg(struct imx290 *imx290, u16 address_low, - u8 nr_regs, u32 value) -{ - unsigned int i; - int ret; - - ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x01); - if (ret) { - dev_err(imx290->dev, "Error setting hold register\n"); - return ret; - } - - for (i = 0; i < nr_regs; i++) { - ret = imx290_write_reg(imx290, address_low + i, - (u8)(value >> (i * 8))); - if (ret) { - dev_err(imx290->dev, "Error writing buffered registers\n"); - return ret; - } - } - - ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x00); - if (ret) { - dev_err(imx290->dev, "Error setting hold register\n"); - return ret; - } - - return ret; -} - -static int imx290_set_gain(struct imx290 *imx290, u32 value) -{ - int ret; - - ret = imx290_write_buffered_reg(imx290, IMX290_GAIN, 1, value); - if (ret) - dev_err(imx290->dev, "Unable to write gain\n"); - - return ret; -} - /* Stop streaming */ static int imx290_stop_streaming(struct imx290 *imx290) { - int ret; + int ret = 0; - ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x01); - if (ret < 0) - return ret; + imx290_write(imx290, IMX290_STANDBY, 0x01, &ret); msleep(30); - return imx290_write_reg(imx290, IMX290_XMSTA, 0x01); + return imx290_write(imx290, IMX290_XMSTA, 0x01, &ret); } static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) @@ -477,28 +524,32 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) return 0; switch (ctrl->id) { - case V4L2_CID_GAIN: - ret = imx290_set_gain(imx290, ctrl->val); + case V4L2_CID_ANALOGUE_GAIN: + ret = imx290_write(imx290, IMX290_GAIN, ctrl->val, NULL); + break; + + case V4L2_CID_EXPOSURE: + ret = imx290_write(imx290, IMX290_SHS1, + IMX290_VMAX_DEFAULT - ctrl->val - 1, NULL); break; + case V4L2_CID_TEST_PATTERN: if (ctrl->val) { - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, 0x00); - imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); + imx290_write(imx290, IMX290_BLKLEVEL, 0, &ret); usleep_range(10000, 11000); - imx290_write_reg(imx290, IMX290_PGCTRL, - (u8)(IMX290_PGCTRL_REGEN | - IMX290_PGCTRL_THRU | - IMX290_PGCTRL_MODE(ctrl->val))); + imx290_write(imx290, IMX290_PGCTRL, + (u8)(IMX290_PGCTRL_REGEN | + IMX290_PGCTRL_THRU | + IMX290_PGCTRL_MODE(ctrl->val)), &ret); } else { - imx290_write_reg(imx290, IMX290_PGCTRL, 0x00); + imx290_write(imx290, IMX290_PGCTRL, 0x00, &ret); usleep_range(10000, 11000); if (imx290->bpp == 10) - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, - 0x3c); + imx290_write(imx290, IMX290_BLKLEVEL, 0x3c, + &ret); else /* 12 bits per pixel */ - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, - 0xf0); - imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); + imx290_write(imx290, IMX290_BLKLEVEL, 0xf0, + &ret); } break; default: @@ -515,6 +566,16 @@ static const struct v4l2_ctrl_ops imx290_ctrl_ops = { .s_ctrl = imx290_set_ctrl, }; +static struct v4l2_mbus_framefmt * +imx290_get_pad_format(struct imx290 *imx290, struct v4l2_subdev_state *state, + u32 which) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + return &imx290->current_format; + else + return v4l2_subdev_get_try_format(&imx290->sd, state, 0); +} + static int imx290_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -558,12 +619,7 @@ static int imx290_get_fmt(struct v4l2_subdev *sd, mutex_lock(&imx290->lock); - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) - framefmt = v4l2_subdev_get_try_format(&imx290->sd, sd_state, - fmt->pad); - else - framefmt = &imx290->current_format; - + framefmt = imx290_get_pad_format(imx290, sd_state, fmt->which); fmt->format = *framefmt; mutex_unlock(&imx290->lock); @@ -623,10 +679,9 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, fmt->format.code = imx290_formats[i].code; fmt->format.field = V4L2_FIELD_NONE; - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - format = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); - } else { - format = &imx290->current_format; + format = imx290_get_pad_format(imx290, sd_state, fmt->which); + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { imx290->current_mode = mode; imx290->bpp = imx290_formats[i].bpp; @@ -636,6 +691,20 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, if (imx290->pixel_rate) __v4l2_ctrl_s_ctrl_int64(imx290->pixel_rate, imx290_calc_pixel_rate(imx290)); + + if (imx290->hblank) { + unsigned int hblank = mode->hmax - mode->width; + + __v4l2_ctrl_modify_range(imx290->hblank, hblank, hblank, + 1, hblank); + } + + if (imx290->vblank) { + unsigned int vblank = IMX290_VMAX_DEFAULT - mode->height; + + __v4l2_ctrl_modify_range(imx290->vblank, vblank, vblank, + 1, vblank); + } } *format = fmt->format; @@ -645,6 +714,52 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, return 0; } +static int imx290_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct imx290 *imx290 = to_imx290(sd); + struct v4l2_mbus_framefmt *format; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + format = imx290_get_pad_format(imx290, sd_state, sel->which); + + mutex_lock(&imx290->lock); + + sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP + + (IMX290_PIXEL_ARRAY_RECORDING_HEIGHT - format->height) / 2; + sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT + + (IMX290_PIXEL_ARRAY_RECORDING_WIDTH - format->width) / 2; + sel->r.width = format->width; + sel->r.height = format->height; + + mutex_unlock(&imx290->lock); + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = IMX290_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX290_PIXEL_ARRAY_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP; + sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT; + sel->r.width = IMX290_PIXEL_ARRAY_RECORDING_WIDTH; + sel->r.height = IMX290_PIXEL_ARRAY_RECORDING_HEIGHT; + + return 0; + + default: + return -EINVAL; + } +} + static int imx290_entity_init_cfg(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state) { @@ -690,25 +805,6 @@ static int imx290_write_current_format(struct imx290 *imx290) return 0; } -static int imx290_set_hmax(struct imx290 *imx290, u32 val) -{ - int ret; - - ret = imx290_write_reg(imx290, IMX290_HMAX_LOW, (val & 0xff)); - if (ret) { - dev_err(imx290->dev, "Error setting HMAX register\n"); - return ret; - } - - ret = imx290_write_reg(imx290, IMX290_HMAX_HIGH, ((val >> 8) & 0xff)); - if (ret) { - dev_err(imx290->dev, "Error setting HMAX register\n"); - return ret; - } - - return 0; -} - /* Start streaming */ static int imx290_start_streaming(struct imx290 *imx290) { @@ -737,8 +833,10 @@ static int imx290_start_streaming(struct imx290 *imx290) dev_err(imx290->dev, "Could not set current mode\n"); return ret; } - ret = imx290_set_hmax(imx290, imx290->current_mode->hmax); - if (ret < 0) + + ret = imx290_write(imx290, IMX290_HMAX, imx290->current_mode->hmax, + NULL); + if (ret) return ret; /* Apply customized values from user */ @@ -748,14 +846,12 @@ static int imx290_start_streaming(struct imx290 *imx290) return ret; } - ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x00); - if (ret < 0) - return ret; + imx290_write(imx290, IMX290_STANDBY, 0x00, &ret); msleep(30); /* Start streaming */ - return imx290_write_reg(imx290, IMX290_XMSTA, 0x00); + return imx290_write(imx290, IMX290_XMSTA, 0x00, &ret); } static int imx290_set_stream(struct v4l2_subdev *sd, int enable) @@ -788,10 +884,10 @@ static int imx290_get_regulators(struct device *dev, struct imx290 *imx290) { unsigned int i; - for (i = 0; i < IMX290_NUM_SUPPLIES; i++) + for (i = 0; i < ARRAY_SIZE(imx290->supplies); i++) imx290->supplies[i].supply = imx290_supply_name[i]; - return devm_regulator_bulk_get(dev, IMX290_NUM_SUPPLIES, + return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx290->supplies), imx290->supplies); } @@ -814,27 +910,13 @@ static int imx290_set_data_lanes(struct imx290 *imx290) * validated in probe itself */ dev_err(imx290->dev, "Lane configuration not supported\n"); - ret = -EINVAL; - goto exit; - } - - ret = imx290_write_reg(imx290, IMX290_PHY_LANE_NUM, laneval); - if (ret) { - dev_err(imx290->dev, "Error setting Physical Lane number register\n"); - goto exit; - } - - ret = imx290_write_reg(imx290, IMX290_CSI_LANE_MODE, laneval); - if (ret) { - dev_err(imx290->dev, "Error setting CSI Lane mode register\n"); - goto exit; + return -EINVAL; } - ret = imx290_write_reg(imx290, IMX290_FR_FDG_SEL, frsel); - if (ret) - dev_err(imx290->dev, "Error setting FR/FDG SEL register\n"); + imx290_write(imx290, IMX290_PHY_LANE_NUM, laneval, &ret); + imx290_write(imx290, IMX290_CSI_LANE_MODE, laneval, &ret); + imx290_write(imx290, IMX290_FR_FDG_SEL, frsel, &ret); -exit: return ret; } @@ -850,7 +932,8 @@ static int imx290_power_on(struct device *dev) return ret; } - ret = regulator_bulk_enable(IMX290_NUM_SUPPLIES, imx290->supplies); + ret = regulator_bulk_enable(ARRAY_SIZE(imx290->supplies), + imx290->supplies); if (ret) { dev_err(dev, "Failed to enable regulators\n"); clk_disable_unprepare(imx290->xclk); @@ -874,7 +957,7 @@ static int imx290_power_off(struct device *dev) clk_disable_unprepare(imx290->xclk); gpiod_set_value_cansleep(imx290->rst_gpio, 1); - regulator_bulk_disable(IMX290_NUM_SUPPLIES, imx290->supplies); + regulator_bulk_disable(ARRAY_SIZE(imx290->supplies), imx290->supplies); return 0; } @@ -893,6 +976,7 @@ static const struct v4l2_subdev_pad_ops imx290_pad_ops = { .enum_frame_size = imx290_enum_frame_size, .get_fmt = imx290_get_fmt, .set_fmt = imx290_set_fmt, + .get_selection = imx290_get_selection, }; static const struct v4l2_subdev_ops imx290_subdev_ops = { @@ -904,6 +988,85 @@ static const struct media_entity_operations imx290_subdev_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; +static int imx290_ctrl_init(struct imx290 *imx290) +{ + struct v4l2_fwnode_device_properties props; + unsigned int blank; + int ret; + + ret = v4l2_fwnode_device_parse(imx290->dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(&imx290->ctrls, 9); + imx290->ctrls.lock = &imx290->lock; + + /* + * 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. + * + * The IMX327 and IMX462 are largely compatible with the IMX290, but + * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital + * gain. When support for those sensors gets added to the driver, the + * gain control should be adjusted accordingly. + */ + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, 0, 100, 1, 0); + + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1, + IMX290_VMAX_DEFAULT - 2); + + imx290->link_freq = + v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_LINK_FREQ, + imx290_link_freqs_num(imx290) - 1, 0, + imx290_link_freqs_ptr(imx290)); + if (imx290->link_freq) + imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, + imx290_calc_pixel_rate(imx290)); + + v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx290_test_pattern_menu) - 1, + 0, 0, imx290_test_pattern_menu); + + blank = imx290->current_mode->hmax - imx290->current_mode->width; + imx290->hblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_HBLANK, blank, blank, 1, + blank); + if (imx290->hblank) + imx290->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + blank = IMX290_VMAX_DEFAULT - imx290->current_mode->height; + imx290->vblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_VBLANK, blank, blank, 1, + blank); + if (imx290->vblank) + imx290->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_fwnode_properties(&imx290->ctrls, &imx290_ctrl_ops, + &props); + + imx290->sd.ctrl_handler = &imx290->ctrls; + + if (imx290->ctrls.error) { + ret = imx290->ctrls.error; + v4l2_ctrl_handler_free(&imx290->ctrls); + return ret; + } + + return 0; +} + /* * Returns 0 if all link frequencies used by the driver for the given number * of MIPI data lanes are mentioned in the device tree, or the value of the @@ -1042,36 +1205,10 @@ static int imx290_probe(struct i2c_client *client) */ imx290_entity_init_cfg(&imx290->sd, NULL); - v4l2_ctrl_handler_init(&imx290->ctrls, 4); - - v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_GAIN, 0, 72, 1, 0); - - imx290->link_freq = - v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_LINK_FREQ, - imx290_link_freqs_num(imx290) - 1, 0, - imx290_link_freqs_ptr(imx290)); - if (imx290->link_freq) - imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_PIXEL_RATE, - 1, INT_MAX, 1, - imx290_calc_pixel_rate(imx290)); - - v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(imx290_test_pattern_menu) - 1, - 0, 0, imx290_test_pattern_menu); - - imx290->sd.ctrl_handler = &imx290->ctrls; - - if (imx290->ctrls.error) { - dev_err(dev, "Control initialization error %d\n", - imx290->ctrls.error); - ret = imx290->ctrls.error; - goto free_ctrl; + ret = imx290_ctrl_init(imx290); + if (ret < 0) { + dev_err(dev, "Control initialization error %d\n", ret); + goto free_mutex; } v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops); @@ -1112,6 +1249,7 @@ free_entity: media_entity_cleanup(&imx290->sd.entity); free_ctrl: v4l2_ctrl_handler_free(&imx290->ctrls); +free_mutex: mutex_destroy(&imx290->lock); free_err: v4l2_fwnode_endpoint_free(&ep); diff --git a/drivers/media/i2c/imx319.c b/drivers/media/i2c/imx319.c index 245a18fb40ad..45b1b61b2880 100644 --- a/drivers/media/i2c/imx319.c +++ b/drivers/media/i2c/imx319.c @@ -2328,8 +2328,12 @@ static int imx319_init_controls(struct imx319 *imx319) imx319->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx319->hflip) + imx319->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; imx319->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx319->vflip) + imx319->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX319_ANA_GAIN_MIN, IMX319_ANA_GAIN_MAX, diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c index b46178681c05..25d4dbb6041e 100644 --- a/drivers/media/i2c/imx355.c +++ b/drivers/media/i2c/imx355.c @@ -1617,8 +1617,12 @@ static int imx355_init_controls(struct imx355 *imx355) imx355->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx355->hflip) + imx355->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; imx355->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx355->vflip) + imx355->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX355_ANA_GAIN_MIN, IMX355_ANA_GAIN_MAX, diff --git a/drivers/media/i2c/imx412.c b/drivers/media/i2c/imx412.c index 7f6d29e0e7c4..e1e986dc8856 100644 --- a/drivers/media/i2c/imx412.c +++ b/drivers/media/i2c/imx412.c @@ -1172,6 +1172,7 @@ static int imx412_init_controls(struct imx412 *imx412) static int imx412_probe(struct i2c_client *client) { struct imx412 *imx412; + const char *name; int ret; imx412 = devm_kzalloc(&client->dev, sizeof(*imx412), GFP_KERNEL); @@ -1179,6 +1180,9 @@ static int imx412_probe(struct i2c_client *client) return -ENOMEM; imx412->dev = &client->dev; + name = device_get_match_data(&client->dev); + if (!name) + return -ENODEV; /* Initialize subdev */ v4l2_i2c_subdev_init(&imx412->sd, client, &imx412_subdev_ops); @@ -1218,6 +1222,8 @@ static int imx412_probe(struct i2c_client *client) imx412->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; imx412->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + v4l2_i2c_subdev_set_name(&imx412->sd, client, name, NULL); + /* Initialize source pad */ imx412->pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&imx412->sd.entity, 1, &imx412->pad); @@ -1279,7 +1285,8 @@ static const struct dev_pm_ops imx412_pm_ops = { }; static const struct of_device_id imx412_of_match[] = { - { .compatible = "sony,imx412" }, + { .compatible = "sony,imx412", .data = "imx412" }, + { .compatible = "sony,imx577", .data = "imx577" }, { } }; diff --git a/drivers/media/i2c/isl7998x.c b/drivers/media/i2c/isl7998x.c index 20f548a8a054..ae7af2cc94f5 100644 --- a/drivers/media/i2c/isl7998x.c +++ b/drivers/media/i2c/isl7998x.c @@ -665,7 +665,7 @@ static int isl7998x_set_standard(struct isl7998x *isl7998x, v4l2_std_id norm) static int isl7998x_init(struct isl7998x *isl7998x) { const unsigned int lanes = isl7998x->nr_mipi_lanes; - const u32 isl7998x_video_in_chan_map[] = { 0x00, 0x11, 0x02, 0x02 }; + static const u32 isl7998x_video_in_chan_map[] = { 0x00, 0x11, 0x02, 0x02 }; const struct reg_sequence isl7998x_init_seq_custom[] = { { ISL7998X_REG_P0_VIDEO_IN_CHAN_CTL, isl7998x_video_in_chan_map[isl7998x->nr_inputs - 1] }, diff --git a/drivers/media/i2c/ks0127.c b/drivers/media/i2c/ks0127.c index 215d9a43b0b9..0d86f2db7ad2 100644 --- a/drivers/media/i2c/ks0127.c +++ b/drivers/media/i2c/ks0127.c @@ -650,7 +650,7 @@ static const struct v4l2_subdev_ops ks0127_ops = { /* ----------------------------------------------------------------------- */ -static int ks0127_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int ks0127_probe(struct i2c_client *client) { struct ks0127 *ks; struct v4l2_subdev *sd; @@ -696,7 +696,7 @@ static struct i2c_driver ks0127_driver = { .driver = { .name = "ks0127", }, - .probe = ks0127_probe, + .probe_new = ks0127_probe, .remove = ks0127_remove, .id_table = ks0127_id, }; diff --git a/drivers/media/i2c/lm3560.c b/drivers/media/i2c/lm3560.c index edad3138cb07..5ef613604be7 100644 --- a/drivers/media/i2c/lm3560.c +++ b/drivers/media/i2c/lm3560.c @@ -391,8 +391,7 @@ static int lm3560_init_device(struct lm3560_flash *flash) return rval; } -static int lm3560_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int lm3560_probe(struct i2c_client *client) { struct lm3560_flash *flash; struct lm3560_platform_data *pdata = dev_get_platdata(&client->dev); @@ -468,7 +467,7 @@ static struct i2c_driver lm3560_i2c_driver = { .name = LM3560_NAME, .pm = NULL, }, - .probe = lm3560_probe, + .probe_new = lm3560_probe, .remove = lm3560_remove, .id_table = lm3560_id_table, }; diff --git a/drivers/media/i2c/lm3646.c b/drivers/media/i2c/lm3646.c index 0aaa963917d8..2a0cf74d2bed 100644 --- a/drivers/media/i2c/lm3646.c +++ b/drivers/media/i2c/lm3646.c @@ -334,8 +334,7 @@ static int lm3646_init_device(struct lm3646_flash *flash) return regmap_read(flash->regmap, REG_FLAG, ®_val); } -static int lm3646_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int lm3646_probe(struct i2c_client *client) { struct lm3646_flash *flash; struct lm3646_platform_data *pdata = dev_get_platdata(&client->dev); @@ -397,7 +396,7 @@ static struct i2c_driver lm3646_i2c_driver = { .driver = { .name = LM3646_NAME, }, - .probe = lm3646_probe, + .probe_new = lm3646_probe, .remove = lm3646_remove, .id_table = lm3646_id_table, }; diff --git a/drivers/media/i2c/m52790.c b/drivers/media/i2c/m52790.c index 2ab91b993c33..0e6507ab7e08 100644 --- a/drivers/media/i2c/m52790.c +++ b/drivers/media/i2c/m52790.c @@ -129,8 +129,7 @@ static const struct v4l2_subdev_ops m52790_ops = { /* i2c implementation */ -static int m52790_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int m52790_probe(struct i2c_client *client) { struct m52790_state *state; struct v4l2_subdev *sd; @@ -173,7 +172,7 @@ static struct i2c_driver m52790_driver = { .driver = { .name = "m52790", }, - .probe = m52790_probe, + .probe_new = m52790_probe, .remove = m52790_remove, .id_table = m52790_id, }; diff --git a/drivers/media/i2c/m5mols/m5mols_core.c b/drivers/media/i2c/m5mols/m5mols_core.c index 2201d2a26353..2b01873ba0db 100644 --- a/drivers/media/i2c/m5mols/m5mols_core.c +++ b/drivers/media/i2c/m5mols/m5mols_core.c @@ -939,8 +939,7 @@ static irqreturn_t m5mols_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static int m5mols_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int m5mols_probe(struct i2c_client *client) { const struct m5mols_platform_data *pdata = client->dev.platform_data; struct m5mols_info *info; @@ -1039,7 +1038,7 @@ static struct i2c_driver m5mols_i2c_driver = { .driver = { .name = MODULE_NAME, }, - .probe = m5mols_probe, + .probe_new = m5mols_probe, .remove = m5mols_remove, .id_table = m5mols_id, }; diff --git a/drivers/media/i2c/ml86v7667.c b/drivers/media/i2c/ml86v7667.c index 49ec59b0ca43..dbd2f0bd3651 100644 --- a/drivers/media/i2c/ml86v7667.c +++ b/drivers/media/i2c/ml86v7667.c @@ -359,8 +359,7 @@ static int ml86v7667_init(struct ml86v7667_priv *priv) return ret; } -static int ml86v7667_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ml86v7667_probe(struct i2c_client *client) { struct ml86v7667_priv *priv; int ret; @@ -434,7 +433,7 @@ static struct i2c_driver ml86v7667_i2c_driver = { .driver = { .name = DRV_NAME, }, - .probe = ml86v7667_probe, + .probe_new = ml86v7667_probe, .remove = ml86v7667_remove, .id_table = ml86v7667_id, }; diff --git a/drivers/media/i2c/mt9m032.c b/drivers/media/i2c/mt9m032.c index 76b8c9c08c82..958cfdd73d57 100644 --- a/drivers/media/i2c/mt9m032.c +++ b/drivers/media/i2c/mt9m032.c @@ -701,8 +701,7 @@ static const struct v4l2_subdev_ops mt9m032_ops = { * Driver initialization and probing */ -static int mt9m032_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int mt9m032_probe(struct i2c_client *client) { struct mt9m032_platform_data *pdata = client->dev.platform_data; struct i2c_adapter *adapter = client->adapter; @@ -880,7 +879,7 @@ static struct i2c_driver mt9m032_i2c_driver = { .driver = { .name = MT9M032_NAME, }, - .probe = mt9m032_probe, + .probe_new = mt9m032_probe, .remove = mt9m032_remove, .id_table = mt9m032_id_table, }; diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c index 45f7b5e52bc3..4ffc2f6e7db4 100644 --- a/drivers/media/i2c/mt9p031.c +++ b/drivers/media/i2c/mt9p031.c @@ -307,6 +307,7 @@ static inline int mt9p031_pll_disable(struct mt9p031 *mt9p031) static int mt9p031_power_on(struct mt9p031 *mt9p031) { + unsigned long rate, delay; int ret; /* Ensure RESET_BAR is active */ @@ -334,7 +335,12 @@ static int mt9p031_power_on(struct mt9p031 *mt9p031) /* Now RESET_BAR must be high */ if (mt9p031->reset) { gpiod_set_value(mt9p031->reset, 0); - usleep_range(1000, 2000); + /* Wait 850000 EXTCLK cycles before de-asserting reset. */ + rate = clk_get_rate(mt9p031->clk); + if (!rate) + rate = 6000000; /* Slowest supported clock, 6 MHz */ + delay = DIV_ROUND_UP(850000 * 1000, rate); + msleep(delay); } return 0; @@ -702,7 +708,6 @@ static int mt9p031_init_cfg(struct v4l2_subdev *subdev, V4L2_SUBDEV_FORMAT_TRY; crop = __mt9p031_get_pad_crop(mt9p031, sd_state, 0, which); - v4l2_subdev_get_try_crop(subdev, sd_state, 0); crop->left = MT9P031_COLUMN_START_DEF; crop->top = MT9P031_ROW_START_DEF; crop->width = MT9P031_WINDOW_WIDTH_DEF; diff --git a/drivers/media/i2c/mt9t001.c b/drivers/media/i2c/mt9t001.c index d5abe4a7ef07..c635ed11388a 100644 --- a/drivers/media/i2c/mt9t001.c +++ b/drivers/media/i2c/mt9t001.c @@ -856,8 +856,7 @@ static const struct v4l2_subdev_internal_ops mt9t001_subdev_internal_ops = { .close = mt9t001_close, }; -static int mt9t001_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int mt9t001_probe(struct i2c_client *client) { struct mt9t001_platform_data *pdata = client->dev.platform_data; struct mt9t001 *mt9t001; @@ -981,7 +980,7 @@ static struct i2c_driver mt9t001_driver = { .driver = { .name = "mt9t001", }, - .probe = mt9t001_probe, + .probe_new = mt9t001_probe, .remove = mt9t001_remove, .id_table = mt9t001_id, }; diff --git a/drivers/media/i2c/mt9t112.c b/drivers/media/i2c/mt9t112.c index ad564095d0cf..a82f056787b8 100644 --- a/drivers/media/i2c/mt9t112.c +++ b/drivers/media/i2c/mt9t112.c @@ -1060,8 +1060,7 @@ done: return ret; } -static int mt9t112_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int mt9t112_probe(struct i2c_client *client) { struct mt9t112_priv *priv; int ret; @@ -1120,7 +1119,7 @@ static struct i2c_driver mt9t112_i2c_driver = { .driver = { .name = "mt9t112", }, - .probe = mt9t112_probe, + .probe_new = mt9t112_probe, .remove = mt9t112_remove, .id_table = mt9t112_id, }; diff --git a/drivers/media/i2c/mt9v011.c b/drivers/media/i2c/mt9v011.c index 9952ce06ebb2..c54c7fbf0963 100644 --- a/drivers/media/i2c/mt9v011.c +++ b/drivers/media/i2c/mt9v011.c @@ -478,8 +478,7 @@ static const struct v4l2_subdev_ops mt9v011_ops = { I2C Client & Driver ****************************************************************************/ -static int mt9v011_probe(struct i2c_client *c, - const struct i2c_device_id *id) +static int mt9v011_probe(struct i2c_client *c) { u16 version; struct mt9v011 *core; @@ -586,7 +585,7 @@ static struct i2c_driver mt9v011_driver = { .driver = { .name = "mt9v011", }, - .probe = mt9v011_probe, + .probe_new = mt9v011_probe, .remove = mt9v011_remove, .id_table = mt9v011_id, }; diff --git a/drivers/media/i2c/noon010pc30.c b/drivers/media/i2c/noon010pc30.c index ecaf5e9057f1..144bef2835f7 100644 --- a/drivers/media/i2c/noon010pc30.c +++ b/drivers/media/i2c/noon010pc30.c @@ -702,8 +702,7 @@ static int noon010_detect(struct i2c_client *client, struct noon010_info *info) return ret == NOON010PC30_ID ? 0 : -ENODEV; } -static int noon010_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int noon010_probe(struct i2c_client *client) { struct noon010_info *info; struct v4l2_subdev *sd; @@ -810,7 +809,7 @@ static struct i2c_driver noon010_i2c_driver = { .driver = { .name = MODULE_NAME }, - .probe = noon010_probe, + .probe_new = noon010_probe, .remove = noon010_remove, .id_table = noon010_id, }; diff --git a/drivers/media/i2c/ov08d10.c b/drivers/media/i2c/ov08d10.c index c1703596c3dc..a39e086a51c5 100644 --- a/drivers/media/i2c/ov08d10.c +++ b/drivers/media/i2c/ov08d10.c @@ -990,8 +990,13 @@ static int ov08d10_init_controls(struct ov08d10 *ov08d10) ov08d10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov08d10_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (ov08d10->hflip) + ov08d10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ov08d10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov08d10_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (ov08d10->vflip) + ov08d10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + if (ctrl_hdlr->error) return ctrl_hdlr->error; diff --git a/drivers/media/i2c/ov08x40.c b/drivers/media/i2c/ov08x40.c new file mode 100644 index 000000000000..72ae7fba94eb --- /dev/null +++ b/drivers/media/i2c/ov08x40.c @@ -0,0 +1,3325 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022 Intel Corporation. + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define OV08X40_REG_VALUE_08BIT 1 +#define OV08X40_REG_VALUE_16BIT 2 +#define OV08X40_REG_VALUE_24BIT 3 + +#define OV08X40_REG_MODE_SELECT 0x0100 +#define OV08X40_MODE_STANDBY 0x00 +#define OV08X40_MODE_STREAMING 0x01 + +#define OV08X40_REG_AO_STANDBY 0x1000 +#define OV08X40_AO_STREAMING 0x04 + +#define OV08X40_REG_MS_SELECT 0x1001 +#define OV08X40_MS_STANDBY 0x00 +#define OV08X40_MS_STREAMING 0x04 + +#define OV08X40_REG_SOFTWARE_RST 0x0103 +#define OV08X40_SOFTWARE_RST 0x01 + +/* Chip ID */ +#define OV08X40_REG_CHIP_ID 0x300a +#define OV08X40_CHIP_ID 0x560858 + +/* V_TIMING internal */ +#define OV08X40_REG_VTS 0x380e +#define OV08X40_VTS_30FPS 0x1388 +#define OV08X40_VTS_BIN_30FPS 0x115c +#define OV08X40_VTS_MAX 0x7fff + +/* H TIMING internal */ +#define OV08X40_REG_HTS 0x380c +#define OV08X40_HTS_30FPS 0x0280 + +/* Exposure control */ +#define OV08X40_REG_EXPOSURE 0x3500 +#define OV08X40_EXPOSURE_MAX_MARGIN 31 +#define OV08X40_EXPOSURE_MIN 1 +#define OV08X40_EXPOSURE_STEP 1 +#define OV08X40_EXPOSURE_DEFAULT 0x40 + +/* Short Exposure control */ +#define OV08X40_REG_SHORT_EXPOSURE 0x3540 + +/* Analog gain control */ +#define OV08X40_REG_ANALOG_GAIN 0x3508 +#define OV08X40_ANA_GAIN_MIN 0x80 +#define OV08X40_ANA_GAIN_MAX 0x07c0 +#define OV08X40_ANA_GAIN_STEP 1 +#define OV08X40_ANA_GAIN_DEFAULT 0x80 + +/* Digital gain control */ +#define OV08X40_REG_DGTL_GAIN_H 0x350a +#define OV08X40_REG_DGTL_GAIN_M 0x350b +#define OV08X40_REG_DGTL_GAIN_L 0x350c + +#define OV08X40_DGTL_GAIN_MIN 1024 /* Min = 1 X */ +#define OV08X40_DGTL_GAIN_MAX (4096 - 1) /* Max = 4 X */ +#define OV08X40_DGTL_GAIN_DEFAULT 2560 /* Default gain = 2.5 X */ +#define OV08X40_DGTL_GAIN_STEP 1 /* Each step = 1/1024 */ + +#define OV08X40_DGTL_GAIN_L_SHIFT 6 +#define OV08X40_DGTL_GAIN_L_MASK 0x3 +#define OV08X40_DGTL_GAIN_M_SHIFT 2 +#define OV08X40_DGTL_GAIN_M_MASK 0xff +#define OV08X40_DGTL_GAIN_H_SHIFT 10 +#define OV08X40_DGTL_GAIN_H_MASK 0x1F + +/* Test Pattern Control */ +#define OV08X40_REG_TEST_PATTERN 0x50C1 +#define OV08X40_REG_ISP 0x5000 +#define OV08X40_REG_SHORT_TEST_PATTERN 0x53C1 +#define OV08X40_TEST_PATTERN_ENABLE BIT(0) +#define OV08X40_TEST_PATTERN_MASK 0xcf +#define OV08X40_TEST_PATTERN_BAR_SHIFT 4 + +/* Flip Control */ +#define OV08X40_REG_VFLIP 0x3820 +#define OV08X40_REG_MIRROR 0x3821 + +/* Horizontal Window Offset */ +#define OV08X40_REG_H_WIN_OFFSET 0x3811 + +/* Vertical Window Offset */ +#define OV08X40_REG_V_WIN_OFFSET 0x3813 + +enum { + OV08X40_LINK_FREQ_400MHZ_INDEX, +}; + +struct ov08x40_reg { + u16 address; + u8 val; +}; + +struct ov08x40_reg_list { + u32 num_of_regs; + const struct ov08x40_reg *regs; +}; + +/* Link frequency config */ +struct ov08x40_link_freq_config { + u32 pixels_per_line; + + /* registers for this link frequency */ + struct ov08x40_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct ov08x40_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + u32 lanes; + /* V-timing */ + u32 vts_def; + u32 vts_min; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct ov08x40_reg_list reg_list; +}; + +static const struct ov08x40_reg mipi_data_rate_800mbps[] = { + {0x0103, 0x01}, + {0x1000, 0x00}, + {0x1601, 0xd0}, + {0x1001, 0x04}, + {0x5004, 0x53}, + {0x5110, 0x00}, + {0x5111, 0x14}, + {0x5112, 0x01}, + {0x5113, 0x7b}, + {0x5114, 0x00}, + {0x5152, 0xa3}, + {0x5a52, 0x1f}, + {0x5a1a, 0x0e}, + {0x5a1b, 0x10}, + {0x5a1f, 0x0e}, + {0x5a27, 0x0e}, + {0x6002, 0x2e}, +}; + +static const struct ov08x40_reg mode_3856x2416_regs[] = { + {0x5000, 0x5d}, + {0x5001, 0x20}, + {0x5008, 0xb0}, + {0x50c1, 0x00}, + {0x53c1, 0x00}, + {0x5f40, 0x00}, + {0x5f41, 0x40}, + {0x0300, 0x3a}, + {0x0301, 0xc8}, + {0x0302, 0x31}, + {0x0303, 0x03}, + {0x0304, 0x01}, + {0x0305, 0xa1}, + {0x0306, 0x04}, + {0x0307, 0x01}, + {0x0308, 0x03}, + {0x0309, 0x03}, + {0x0310, 0x0a}, + {0x0311, 0x02}, + {0x0312, 0x01}, + {0x0313, 0x08}, + {0x0314, 0x66}, + {0x0315, 0x00}, + {0x0316, 0x34}, + {0x0320, 0x02}, + {0x0321, 0x03}, + {0x0323, 0x05}, + {0x0324, 0x01}, + {0x0325, 0xb8}, + {0x0326, 0x4a}, + {0x0327, 0x04}, + {0x0329, 0x00}, + {0x032a, 0x05}, + {0x032b, 0x00}, + {0x032c, 0x00}, + {0x032d, 0x00}, + {0x032e, 0x02}, + {0x032f, 0xa0}, + {0x0350, 0x00}, + {0x0360, 0x01}, + {0x1216, 0x60}, + {0x1217, 0x5b}, + {0x1218, 0x00}, + {0x1220, 0x24}, + {0x198a, 0x00}, + {0x198b, 0x01}, + {0x198e, 0x00}, + {0x198f, 0x01}, + {0x3009, 0x04}, + {0x3012, 0x41}, + {0x3015, 0x00}, + {0x3016, 0xb0}, + {0x3017, 0xf0}, + {0x3018, 0xf0}, + {0x3019, 0xd2}, + {0x301a, 0xb0}, + {0x301c, 0x81}, + {0x301d, 0x02}, + {0x301e, 0x80}, + {0x3022, 0xf0}, + {0x3025, 0x89}, + {0x3030, 0x03}, + {0x3044, 0xc2}, + {0x3050, 0x35}, + {0x3051, 0x60}, + {0x3052, 0x25}, + {0x3053, 0x00}, + {0x3054, 0x00}, + {0x3055, 0x02}, + {0x3056, 0x80}, + {0x3057, 0x80}, + {0x3058, 0x80}, + {0x3059, 0x00}, + {0x3107, 0x86}, + {0x3400, 0x1c}, + {0x3401, 0x80}, + {0x3402, 0x8c}, + {0x3419, 0x13}, + {0x341a, 0x89}, + {0x341b, 0x30}, + {0x3420, 0x00}, + {0x3421, 0x00}, + {0x3422, 0x00}, + {0x3423, 0x00}, + {0x3424, 0x00}, + {0x3425, 0x00}, + {0x3426, 0x00}, + {0x3427, 0x00}, + {0x3428, 0x0f}, + {0x3429, 0x00}, + {0x342a, 0x00}, + {0x342b, 0x00}, + {0x342c, 0x00}, + {0x342d, 0x00}, + {0x342e, 0x00}, + {0x342f, 0x11}, + {0x3430, 0x11}, + {0x3431, 0x10}, + {0x3432, 0x00}, + {0x3433, 0x00}, + {0x3434, 0x00}, + {0x3435, 0x00}, + {0x3436, 0x00}, + {0x3437, 0x00}, + {0x3442, 0x02}, + {0x3443, 0x02}, + {0x3444, 0x07}, + {0x3450, 0x00}, + {0x3451, 0x00}, + {0x3452, 0x18}, + {0x3453, 0x18}, + {0x3454, 0x00}, + {0x3455, 0x80}, + {0x3456, 0x08}, + {0x3500, 0x00}, + {0x3501, 0x02}, + {0x3502, 0x00}, + {0x3504, 0x4c}, + {0x3506, 0x30}, + {0x3507, 0x00}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x01}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x3540, 0x00}, + {0x3541, 0x01}, + {0x3542, 0x00}, + {0x3544, 0x4c}, + {0x3546, 0x30}, + {0x3547, 0x00}, + {0x3548, 0x01}, + {0x3549, 0x00}, + {0x354a, 0x01}, + {0x354b, 0x00}, + {0x354c, 0x00}, + {0x3688, 0x02}, + {0x368a, 0x2e}, + {0x368e, 0x71}, + {0x3696, 0xd1}, + {0x3699, 0x00}, + {0x369a, 0x00}, + {0x36a4, 0x00}, + {0x36a6, 0x00}, + {0x3711, 0x00}, + {0x3712, 0x51}, + {0x3713, 0x00}, + {0x3714, 0x24}, + {0x3716, 0x00}, + {0x3718, 0x07}, + {0x371a, 0x1c}, + {0x371b, 0x00}, + {0x3720, 0x08}, + {0x3725, 0x32}, + {0x3727, 0x05}, + {0x3760, 0x02}, + {0x3761, 0x17}, + {0x3762, 0x02}, + {0x3763, 0x02}, + {0x3764, 0x02}, + {0x3765, 0x2c}, + {0x3766, 0x04}, + {0x3767, 0x2c}, + {0x3768, 0x02}, + {0x3769, 0x00}, + {0x376b, 0x20}, + {0x376e, 0x03}, + {0x37b0, 0x00}, + {0x37b1, 0xab}, + {0x37b2, 0x01}, + {0x37b3, 0x82}, + {0x37b4, 0x00}, + {0x37b5, 0xe4}, + {0x37b6, 0x01}, + {0x37b7, 0xee}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0f}, + {0x3805, 0x1f}, + {0x3806, 0x09}, + {0x3807, 0x7f}, + {0x3808, 0x0f}, + {0x3809, 0x10}, + {0x380a, 0x09}, + {0x380b, 0x70}, + {0x380c, 0x02}, + {0x380d, 0x80}, + {0x380e, 0x13}, + {0x380f, 0x88}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x07}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x00}, + {0x3821, 0x04}, + {0x3822, 0x00}, + {0x3823, 0x04}, + {0x3828, 0x0f}, + {0x382a, 0x80}, + {0x382e, 0x41}, + {0x3837, 0x08}, + {0x383a, 0x81}, + {0x383b, 0x81}, + {0x383c, 0x11}, + {0x383d, 0x11}, + {0x383e, 0x00}, + {0x383f, 0x38}, + {0x3840, 0x00}, + {0x3847, 0x00}, + {0x384a, 0x00}, + {0x384c, 0x02}, + {0x384d, 0x80}, + {0x3856, 0x50}, + {0x3857, 0x30}, + {0x3858, 0x80}, + {0x3859, 0x40}, + {0x3860, 0x00}, + {0x3888, 0x00}, + {0x3889, 0x00}, + {0x388a, 0x00}, + {0x388b, 0x00}, + {0x388c, 0x00}, + {0x388d, 0x00}, + {0x388e, 0x00}, + {0x388f, 0x00}, + {0x3894, 0x00}, + {0x3895, 0x00}, + {0x3c84, 0x00}, + {0x3d85, 0x8b}, + {0x3daa, 0x80}, + {0x3dab, 0x14}, + {0x3dac, 0x80}, + {0x3dad, 0xc8}, + {0x3dae, 0x81}, + {0x3daf, 0x7b}, + {0x3f00, 0x10}, + {0x3f01, 0x11}, + {0x3f06, 0x0d}, + {0x3f07, 0x0b}, + {0x3f08, 0x0d}, + {0x3f09, 0x0b}, + {0x3f0a, 0x01}, + {0x3f0b, 0x11}, + {0x3f0c, 0x33}, + {0x4001, 0x07}, + {0x4007, 0x20}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x08}, + {0x400c, 0x00}, + {0x400d, 0x08}, + {0x400e, 0x14}, + {0x4010, 0xf4}, + {0x4011, 0x03}, + {0x4012, 0x55}, + {0x4015, 0x00}, + {0x4016, 0x2d}, + {0x4017, 0x00}, + {0x4018, 0x0f}, + {0x401b, 0x08}, + {0x401c, 0x00}, + {0x401d, 0x10}, + {0x401e, 0x02}, + {0x401f, 0x00}, + {0x4050, 0x06}, + {0x4051, 0xff}, + {0x4052, 0xff}, + {0x4053, 0xff}, + {0x4054, 0xff}, + {0x4055, 0xff}, + {0x4056, 0xff}, + {0x4057, 0x7f}, + {0x4058, 0x00}, + {0x4059, 0x00}, + {0x405a, 0x00}, + {0x405b, 0x00}, + {0x405c, 0x07}, + {0x405d, 0xff}, + {0x405e, 0x07}, + {0x405f, 0xff}, + {0x4080, 0x78}, + {0x4081, 0x78}, + {0x4082, 0x78}, + {0x4083, 0x78}, + {0x4019, 0x00}, + {0x401a, 0x40}, + {0x4020, 0x04}, + {0x4021, 0x00}, + {0x4022, 0x04}, + {0x4023, 0x00}, + {0x4024, 0x04}, + {0x4025, 0x00}, + {0x4026, 0x04}, + {0x4027, 0x00}, + {0x4030, 0x00}, + {0x4031, 0x00}, + {0x4032, 0x00}, + {0x4033, 0x00}, + {0x4034, 0x00}, + {0x4035, 0x00}, + {0x4036, 0x00}, + {0x4037, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x80}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4060, 0x00}, + {0x4061, 0x00}, + {0x4062, 0x00}, + {0x4063, 0x00}, + {0x4064, 0x00}, + {0x4065, 0x00}, + {0x4066, 0x00}, + {0x4067, 0x00}, + {0x4068, 0x00}, + {0x4069, 0x00}, + {0x406a, 0x00}, + {0x406b, 0x00}, + {0x406c, 0x00}, + {0x406d, 0x00}, + {0x406e, 0x00}, + {0x406f, 0x00}, + {0x4070, 0x00}, + {0x4071, 0x00}, + {0x4072, 0x00}, + {0x4073, 0x00}, + {0x4074, 0x00}, + {0x4075, 0x00}, + {0x4076, 0x00}, + {0x4077, 0x00}, + {0x4078, 0x00}, + {0x4079, 0x00}, + {0x407a, 0x00}, + {0x407b, 0x00}, + {0x407c, 0x00}, + {0x407d, 0x00}, + {0x407e, 0x00}, + {0x407f, 0x00}, + {0x40e0, 0x00}, + {0x40e1, 0x00}, + {0x40e2, 0x00}, + {0x40e3, 0x00}, + {0x40e4, 0x00}, + {0x40e5, 0x00}, + {0x40e6, 0x00}, + {0x40e7, 0x00}, + {0x40e8, 0x00}, + {0x40e9, 0x80}, + {0x40ea, 0x00}, + {0x40eb, 0x80}, + {0x40ec, 0x00}, + {0x40ed, 0x80}, + {0x40ee, 0x00}, + {0x40ef, 0x80}, + {0x40f0, 0x02}, + {0x40f1, 0x04}, + {0x4300, 0x00}, + {0x4301, 0x00}, + {0x4302, 0x00}, + {0x4303, 0x00}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4307, 0x00}, + {0x4308, 0x00}, + {0x4309, 0x00}, + {0x430a, 0x00}, + {0x430b, 0xff}, + {0x430c, 0xff}, + {0x430d, 0x00}, + {0x430e, 0x00}, + {0x4315, 0x00}, + {0x4316, 0x00}, + {0x4317, 0x00}, + {0x4318, 0x00}, + {0x4319, 0x00}, + {0x431a, 0x00}, + {0x431b, 0x00}, + {0x431c, 0x00}, + {0x4500, 0x07}, + {0x4501, 0x00}, + {0x4502, 0x00}, + {0x4503, 0x0f}, + {0x4504, 0x80}, + {0x4506, 0x01}, + {0x4509, 0x05}, + {0x450c, 0x00}, + {0x450d, 0x20}, + {0x450e, 0x00}, + {0x450f, 0x00}, + {0x4510, 0x00}, + {0x4523, 0x00}, + {0x4526, 0x00}, + {0x4542, 0x00}, + {0x4543, 0x00}, + {0x4544, 0x00}, + {0x4545, 0x00}, + {0x4546, 0x00}, + {0x4547, 0x10}, + {0x4602, 0x00}, + {0x4603, 0x15}, + {0x460b, 0x07}, + {0x4680, 0x11}, + {0x4686, 0x00}, + {0x4687, 0x00}, + {0x4700, 0x00}, + {0x4800, 0x64}, + {0x4806, 0x40}, + {0x480b, 0x10}, + {0x480c, 0x80}, + {0x480f, 0x32}, + {0x4813, 0xe4}, + {0x4837, 0x14}, + {0x4850, 0x42}, + {0x4884, 0x04}, + {0x4c00, 0xf8}, + {0x4c01, 0x44}, + {0x4c03, 0x00}, + {0x4d00, 0x00}, + {0x4d01, 0x16}, + {0x4d04, 0x10}, + {0x4d05, 0x00}, + {0x4d06, 0x0c}, + {0x4d07, 0x00}, + {0x3d84, 0x04}, + {0x3680, 0xa4}, + {0x3682, 0x80}, + {0x3601, 0x40}, + {0x3602, 0x90}, + {0x3608, 0x0a}, + {0x3938, 0x09}, + {0x3a74, 0x84}, + {0x3a99, 0x84}, + {0x3ab9, 0xa6}, + {0x3aba, 0xba}, + {0x3b12, 0x84}, + {0x3b14, 0xbb}, + {0x3b15, 0xbf}, + {0x3a29, 0x26}, + {0x3a1f, 0x8a}, + {0x3a22, 0x91}, + {0x3a25, 0x96}, + {0x3a28, 0xb4}, + {0x3a2b, 0xba}, + {0x3a2e, 0xbf}, + {0x3a31, 0xc1}, + {0x3a20, 0x00}, + {0x3939, 0x9d}, + {0x3902, 0x0e}, + {0x3903, 0x0e}, + {0x3904, 0x0e}, + {0x3905, 0x0e}, + {0x3906, 0x07}, + {0x3907, 0x0d}, + {0x3908, 0x11}, + {0x3909, 0x12}, + {0x360f, 0x99}, + {0x390c, 0x33}, + {0x390d, 0x66}, + {0x390e, 0xaa}, + {0x3911, 0x90}, + {0x3913, 0x90}, + {0x3915, 0x90}, + {0x3917, 0x90}, + {0x3b3f, 0x9d}, + {0x3b45, 0x9d}, + {0x3b1b, 0xc9}, + {0x3b21, 0xc9}, + {0x3440, 0xa4}, + {0x3a23, 0x15}, + {0x3a26, 0x1d}, + {0x3a2c, 0x4a}, + {0x3a2f, 0x18}, + {0x3a32, 0x55}, + {0x3b0a, 0x01}, + {0x3b0b, 0x00}, + {0x3b0e, 0x01}, + {0x3b0f, 0x00}, + {0x392c, 0x02}, + {0x392d, 0x02}, + {0x392e, 0x04}, + {0x392f, 0x03}, + {0x3930, 0x08}, + {0x3931, 0x07}, + {0x3932, 0x10}, + {0x3933, 0x0c}, + {0x3609, 0x08}, + {0x3921, 0x0f}, + {0x3928, 0x15}, + {0x3929, 0x2a}, + {0x392a, 0x54}, + {0x392b, 0xa8}, + {0x3426, 0x10}, + {0x3407, 0x01}, + {0x3404, 0x01}, + {0x3500, 0x00}, + {0x3501, 0x10}, + {0x3502, 0x10}, + {0x3508, 0x0f}, + {0x3509, 0x80}, + {0x5a80, 0x75}, + {0x5a81, 0x75}, + {0x5a82, 0x75}, + {0x5a83, 0x75}, + {0x5a84, 0x75}, + {0x5a85, 0x75}, + {0x5a86, 0x75}, + {0x5a87, 0x75}, + {0x5a88, 0x75}, + {0x5a89, 0x75}, + {0x5a8a, 0x75}, + {0x5a8b, 0x75}, + {0x5a8c, 0x75}, + {0x5a8d, 0x75}, + {0x5a8e, 0x75}, + {0x5a8f, 0x75}, + {0x5a90, 0x75}, + {0x5a91, 0x75}, + {0x5a92, 0x75}, + {0x5a93, 0x75}, + {0x5a94, 0x75}, + {0x5a95, 0x75}, + {0x5a96, 0x75}, + {0x5a97, 0x75}, + {0x5a98, 0x75}, + {0x5a99, 0x75}, + {0x5a9a, 0x75}, + {0x5a9b, 0x75}, + {0x5a9c, 0x75}, + {0x5a9d, 0x75}, + {0x5a9e, 0x75}, + {0x5a9f, 0x75}, + {0x5aa0, 0x75}, + {0x5aa1, 0x75}, + {0x5aa2, 0x75}, + {0x5aa3, 0x75}, + {0x5aa4, 0x75}, + {0x5aa5, 0x75}, + {0x5aa6, 0x75}, + {0x5aa7, 0x75}, + {0x5aa8, 0x75}, + {0x5aa9, 0x75}, + {0x5aaa, 0x75}, + {0x5aab, 0x75}, + {0x5aac, 0x75}, + {0x5aad, 0x75}, + {0x5aae, 0x75}, + {0x5aaf, 0x75}, + {0x5ab0, 0x75}, + {0x5ab1, 0x75}, + {0x5ab2, 0x75}, + {0x5ab3, 0x75}, + {0x5ab4, 0x75}, + {0x5ab5, 0x75}, + {0x5ab6, 0x75}, + {0x5ab7, 0x75}, + {0x5ab8, 0x75}, + {0x5ab9, 0x75}, + {0x5aba, 0x75}, + {0x5abb, 0x75}, + {0x5abc, 0x75}, + {0x5abd, 0x75}, + {0x5abe, 0x75}, + {0x5abf, 0x75}, + {0x5ac0, 0x75}, + {0x5ac1, 0x75}, + {0x5ac2, 0x75}, + {0x5ac3, 0x75}, + {0x5ac4, 0x75}, + {0x5ac5, 0x75}, + {0x5ac6, 0x75}, + {0x5ac7, 0x75}, + {0x5ac8, 0x75}, + {0x5ac9, 0x75}, + {0x5aca, 0x75}, + {0x5acb, 0x75}, + {0x5acc, 0x75}, + {0x5acd, 0x75}, + {0x5ace, 0x75}, + {0x5acf, 0x75}, + {0x5ad0, 0x75}, + {0x5ad1, 0x75}, + {0x5ad2, 0x75}, + {0x5ad3, 0x75}, + {0x5ad4, 0x75}, + {0x5ad5, 0x75}, + {0x5ad6, 0x75}, + {0x5ad7, 0x75}, + {0x5ad8, 0x75}, + {0x5ad9, 0x75}, + {0x5ada, 0x75}, + {0x5adb, 0x75}, + {0x5adc, 0x75}, + {0x5add, 0x75}, + {0x5ade, 0x75}, + {0x5adf, 0x75}, + {0x5ae0, 0x75}, + {0x5ae1, 0x75}, + {0x5ae2, 0x75}, + {0x5ae3, 0x75}, + {0x5ae4, 0x75}, + {0x5ae5, 0x75}, + {0x5ae6, 0x75}, + {0x5ae7, 0x75}, + {0x5ae8, 0x75}, + {0x5ae9, 0x75}, + {0x5aea, 0x75}, + {0x5aeb, 0x75}, + {0x5aec, 0x75}, + {0x5aed, 0x75}, + {0x5aee, 0x75}, + {0x5aef, 0x75}, + {0x5af0, 0x75}, + {0x5af1, 0x75}, + {0x5af2, 0x75}, + {0x5af3, 0x75}, + {0x5af4, 0x75}, + {0x5af5, 0x75}, + {0x5af6, 0x75}, + {0x5af7, 0x75}, + {0x5af8, 0x75}, + {0x5af9, 0x75}, + {0x5afa, 0x75}, + {0x5afb, 0x75}, + {0x5afc, 0x75}, + {0x5afd, 0x75}, + {0x5afe, 0x75}, + {0x5aff, 0x75}, + {0x5b00, 0x75}, + {0x5b01, 0x75}, + {0x5b02, 0x75}, + {0x5b03, 0x75}, + {0x5b04, 0x75}, + {0x5b05, 0x75}, + {0x5b06, 0x75}, + {0x5b07, 0x75}, + {0x5b08, 0x75}, + {0x5b09, 0x75}, + {0x5b0a, 0x75}, + {0x5b0b, 0x75}, + {0x5b0c, 0x75}, + {0x5b0d, 0x75}, + {0x5b0e, 0x75}, + {0x5b0f, 0x75}, + {0x5b10, 0x75}, + {0x5b11, 0x75}, + {0x5b12, 0x75}, + {0x5b13, 0x75}, + {0x5b14, 0x75}, + {0x5b15, 0x75}, + {0x5b16, 0x75}, + {0x5b17, 0x75}, + {0x5b18, 0x75}, + {0x5b19, 0x75}, + {0x5b1a, 0x75}, + {0x5b1b, 0x75}, + {0x5b1c, 0x75}, + {0x5b1d, 0x75}, + {0x5b1e, 0x75}, + {0x5b1f, 0x75}, + {0x5b20, 0x75}, + {0x5b21, 0x75}, + {0x5b22, 0x75}, + {0x5b23, 0x75}, + {0x5b24, 0x75}, + {0x5b25, 0x75}, + {0x5b26, 0x75}, + {0x5b27, 0x75}, + {0x5b28, 0x75}, + {0x5b29, 0x75}, + {0x5b2a, 0x75}, + {0x5b2b, 0x75}, + {0x5b2c, 0x75}, + {0x5b2d, 0x75}, + {0x5b2e, 0x75}, + {0x5b2f, 0x75}, + {0x5b30, 0x75}, + {0x5b31, 0x75}, + {0x5b32, 0x75}, + {0x5b33, 0x75}, + {0x5b34, 0x75}, + {0x5b35, 0x75}, + {0x5b36, 0x75}, + {0x5b37, 0x75}, + {0x5b38, 0x75}, + {0x5b39, 0x75}, + {0x5b3a, 0x75}, + {0x5b3b, 0x75}, + {0x5b3c, 0x75}, + {0x5b3d, 0x75}, + {0x5b3e, 0x75}, + {0x5b3f, 0x75}, + {0x5b40, 0x75}, + {0x5b41, 0x75}, + {0x5b42, 0x75}, + {0x5b43, 0x75}, + {0x5b44, 0x75}, + {0x5b45, 0x75}, + {0x5b46, 0x75}, + {0x5b47, 0x75}, + {0x5b48, 0x75}, + {0x5b49, 0x75}, + {0x5b4a, 0x75}, + {0x5b4b, 0x75}, + {0x5b4c, 0x75}, + {0x5b4d, 0x75}, + {0x5b4e, 0x75}, + {0x5b4f, 0x75}, + {0x5b50, 0x75}, + {0x5b51, 0x75}, + {0x5b52, 0x75}, + {0x5b53, 0x75}, + {0x5b54, 0x75}, + {0x5b55, 0x75}, + {0x5b56, 0x75}, + {0x5b57, 0x75}, + {0x5b58, 0x75}, + {0x5b59, 0x75}, + {0x5b5a, 0x75}, + {0x5b5b, 0x75}, + {0x5b5c, 0x75}, + {0x5b5d, 0x75}, + {0x5b5e, 0x75}, + {0x5b5f, 0x75}, + {0x5b60, 0x75}, + {0x5b61, 0x75}, + {0x5b62, 0x75}, + {0x5b63, 0x75}, + {0x5b64, 0x75}, + {0x5b65, 0x75}, + {0x5b66, 0x75}, + {0x5b67, 0x75}, + {0x5b68, 0x75}, + {0x5b69, 0x75}, + {0x5b6a, 0x75}, + {0x5b6b, 0x75}, + {0x5b6c, 0x75}, + {0x5b6d, 0x75}, + {0x5b6e, 0x75}, + {0x5b6f, 0x75}, + {0x5b70, 0x75}, + {0x5b71, 0x75}, + {0x5b72, 0x75}, + {0x5b73, 0x75}, + {0x5b74, 0x75}, + {0x5b75, 0x75}, + {0x5b76, 0x75}, + {0x5b77, 0x75}, + {0x5b78, 0x75}, + {0x5b79, 0x75}, + {0x5b7a, 0x75}, + {0x5b7b, 0x75}, + {0x5b7c, 0x75}, + {0x5b7d, 0x75}, + {0x5b7e, 0x75}, + {0x5b7f, 0x75}, + {0x5b80, 0x75}, + {0x5b81, 0x75}, + {0x5b82, 0x75}, + {0x5b83, 0x75}, + {0x5b84, 0x75}, + {0x5b85, 0x75}, + {0x5b86, 0x75}, + {0x5b87, 0x75}, + {0x5b88, 0x75}, + {0x5b89, 0x75}, + {0x5b8a, 0x75}, + {0x5b8b, 0x75}, + {0x5b8c, 0x75}, + {0x5b8d, 0x75}, + {0x5b8e, 0x75}, + {0x5b8f, 0x75}, + {0x5b90, 0x75}, + {0x5b91, 0x75}, + {0x5b92, 0x75}, + {0x5b93, 0x75}, + {0x5b94, 0x75}, + {0x5b95, 0x75}, + {0x5b96, 0x75}, + {0x5b97, 0x75}, + {0x5b98, 0x75}, + {0x5b99, 0x75}, + {0x5b9a, 0x75}, + {0x5b9b, 0x75}, + {0x5b9c, 0x75}, + {0x5b9d, 0x75}, + {0x5b9e, 0x75}, + {0x5b9f, 0x75}, + {0x5bc0, 0x75}, + {0x5bc1, 0x75}, + {0x5bc2, 0x75}, + {0x5bc3, 0x75}, + {0x5bc4, 0x75}, + {0x5bc5, 0x75}, + {0x5bc6, 0x75}, + {0x5bc7, 0x75}, + {0x5bc8, 0x75}, + {0x5bc9, 0x75}, + {0x5bca, 0x75}, + {0x5bcb, 0x75}, + {0x5bcc, 0x75}, + {0x5bcd, 0x75}, + {0x5bce, 0x75}, + {0x5bcf, 0x75}, + {0x5bd0, 0x75}, + {0x5bd1, 0x75}, + {0x5bd2, 0x75}, + {0x5bd3, 0x75}, + {0x5bd4, 0x75}, + {0x5bd5, 0x75}, + {0x5bd6, 0x75}, + {0x5bd7, 0x75}, + {0x5bd8, 0x75}, + {0x5bd9, 0x75}, + {0x5bda, 0x75}, + {0x5bdb, 0x75}, + {0x5bdc, 0x75}, + {0x5bdd, 0x75}, + {0x5bde, 0x75}, + {0x5bdf, 0x75}, + {0x5be0, 0x75}, + {0x5be1, 0x75}, + {0x5be2, 0x75}, + {0x5be3, 0x75}, + {0x5be4, 0x75}, + {0x5be5, 0x75}, + {0x5be6, 0x75}, + {0x5be7, 0x75}, + {0x5be8, 0x75}, + {0x5be9, 0x75}, + {0x5bea, 0x75}, + {0x5beb, 0x75}, + {0x5bec, 0x75}, + {0x5bed, 0x75}, + {0x5bee, 0x75}, + {0x5bef, 0x75}, + {0x5bf0, 0x75}, + {0x5bf1, 0x75}, + {0x5bf2, 0x75}, + {0x5bf3, 0x75}, + {0x5bf4, 0x75}, + {0x5bf5, 0x75}, + {0x5bf6, 0x75}, + {0x5bf7, 0x75}, + {0x5bf8, 0x75}, + {0x5bf9, 0x75}, + {0x5bfa, 0x75}, + {0x5bfb, 0x75}, + {0x5bfc, 0x75}, + {0x5bfd, 0x75}, + {0x5bfe, 0x75}, + {0x5bff, 0x75}, + {0x5c00, 0x75}, + {0x5c01, 0x75}, + {0x5c02, 0x75}, + {0x5c03, 0x75}, + {0x5c04, 0x75}, + {0x5c05, 0x75}, + {0x5c06, 0x75}, + {0x5c07, 0x75}, + {0x5c08, 0x75}, + {0x5c09, 0x75}, + {0x5c0a, 0x75}, + {0x5c0b, 0x75}, + {0x5c0c, 0x75}, + {0x5c0d, 0x75}, + {0x5c0e, 0x75}, + {0x5c0f, 0x75}, + {0x5c10, 0x75}, + {0x5c11, 0x75}, + {0x5c12, 0x75}, + {0x5c13, 0x75}, + {0x5c14, 0x75}, + {0x5c15, 0x75}, + {0x5c16, 0x75}, + {0x5c17, 0x75}, + {0x5c18, 0x75}, + {0x5c19, 0x75}, + {0x5c1a, 0x75}, + {0x5c1b, 0x75}, + {0x5c1c, 0x75}, + {0x5c1d, 0x75}, + {0x5c1e, 0x75}, + {0x5c1f, 0x75}, + {0x5c20, 0x75}, + {0x5c21, 0x75}, + {0x5c22, 0x75}, + {0x5c23, 0x75}, + {0x5c24, 0x75}, + {0x5c25, 0x75}, + {0x5c26, 0x75}, + {0x5c27, 0x75}, + {0x5c28, 0x75}, + {0x5c29, 0x75}, + {0x5c2a, 0x75}, + {0x5c2b, 0x75}, + {0x5c2c, 0x75}, + {0x5c2d, 0x75}, + {0x5c2e, 0x75}, + {0x5c2f, 0x75}, + {0x5c30, 0x75}, + {0x5c31, 0x75}, + {0x5c32, 0x75}, + {0x5c33, 0x75}, + {0x5c34, 0x75}, + {0x5c35, 0x75}, + {0x5c36, 0x75}, + {0x5c37, 0x75}, + {0x5c38, 0x75}, + {0x5c39, 0x75}, + {0x5c3a, 0x75}, + {0x5c3b, 0x75}, + {0x5c3c, 0x75}, + {0x5c3d, 0x75}, + {0x5c3e, 0x75}, + {0x5c3f, 0x75}, + {0x5c40, 0x75}, + {0x5c41, 0x75}, + {0x5c42, 0x75}, + {0x5c43, 0x75}, + {0x5c44, 0x75}, + {0x5c45, 0x75}, + {0x5c46, 0x75}, + {0x5c47, 0x75}, + {0x5c48, 0x75}, + {0x5c49, 0x75}, + {0x5c4a, 0x75}, + {0x5c4b, 0x75}, + {0x5c4c, 0x75}, + {0x5c4d, 0x75}, + {0x5c4e, 0x75}, + {0x5c4f, 0x75}, + {0x5c50, 0x75}, + {0x5c51, 0x75}, + {0x5c52, 0x75}, + {0x5c53, 0x75}, + {0x5c54, 0x75}, + {0x5c55, 0x75}, + {0x5c56, 0x75}, + {0x5c57, 0x75}, + {0x5c58, 0x75}, + {0x5c59, 0x75}, + {0x5c5a, 0x75}, + {0x5c5b, 0x75}, + {0x5c5c, 0x75}, + {0x5c5d, 0x75}, + {0x5c5e, 0x75}, + {0x5c5f, 0x75}, + {0x5c60, 0x75}, + {0x5c61, 0x75}, + {0x5c62, 0x75}, + {0x5c63, 0x75}, + {0x5c64, 0x75}, + {0x5c65, 0x75}, + {0x5c66, 0x75}, + {0x5c67, 0x75}, + {0x5c68, 0x75}, + {0x5c69, 0x75}, + {0x5c6a, 0x75}, + {0x5c6b, 0x75}, + {0x5c6c, 0x75}, + {0x5c6d, 0x75}, + {0x5c6e, 0x75}, + {0x5c6f, 0x75}, + {0x5c70, 0x75}, + {0x5c71, 0x75}, + {0x5c72, 0x75}, + {0x5c73, 0x75}, + {0x5c74, 0x75}, + {0x5c75, 0x75}, + {0x5c76, 0x75}, + {0x5c77, 0x75}, + {0x5c78, 0x75}, + {0x5c79, 0x75}, + {0x5c7a, 0x75}, + {0x5c7b, 0x75}, + {0x5c7c, 0x75}, + {0x5c7d, 0x75}, + {0x5c7e, 0x75}, + {0x5c7f, 0x75}, + {0x5c80, 0x75}, + {0x5c81, 0x75}, + {0x5c82, 0x75}, + {0x5c83, 0x75}, + {0x5c84, 0x75}, + {0x5c85, 0x75}, + {0x5c86, 0x75}, + {0x5c87, 0x75}, + {0x5c88, 0x75}, + {0x5c89, 0x75}, + {0x5c8a, 0x75}, + {0x5c8b, 0x75}, + {0x5c8c, 0x75}, + {0x5c8d, 0x75}, + {0x5c8e, 0x75}, + {0x5c8f, 0x75}, + {0x5c90, 0x75}, + {0x5c91, 0x75}, + {0x5c92, 0x75}, + {0x5c93, 0x75}, + {0x5c94, 0x75}, + {0x5c95, 0x75}, + {0x5c96, 0x75}, + {0x5c97, 0x75}, + {0x5c98, 0x75}, + {0x5c99, 0x75}, + {0x5c9a, 0x75}, + {0x5c9b, 0x75}, + {0x5c9c, 0x75}, + {0x5c9d, 0x75}, + {0x5c9e, 0x75}, + {0x5c9f, 0x75}, + {0x5ca0, 0x75}, + {0x5ca1, 0x75}, + {0x5ca2, 0x75}, + {0x5ca3, 0x75}, + {0x5ca4, 0x75}, + {0x5ca5, 0x75}, + {0x5ca6, 0x75}, + {0x5ca7, 0x75}, + {0x5ca8, 0x75}, + {0x5ca9, 0x75}, + {0x5caa, 0x75}, + {0x5cab, 0x75}, + {0x5cac, 0x75}, + {0x5cad, 0x75}, + {0x5cae, 0x75}, + {0x5caf, 0x75}, + {0x5cb0, 0x75}, + {0x5cb1, 0x75}, + {0x5cb2, 0x75}, + {0x5cb3, 0x75}, + {0x5cb4, 0x75}, + {0x5cb5, 0x75}, + {0x5cb6, 0x75}, + {0x5cb7, 0x75}, + {0x5cb8, 0x75}, + {0x5cb9, 0x75}, + {0x5cba, 0x75}, + {0x5cbb, 0x75}, + {0x5cbc, 0x75}, + {0x5cbd, 0x75}, + {0x5cbe, 0x75}, + {0x5cbf, 0x75}, + {0x5cc0, 0x75}, + {0x5cc1, 0x75}, + {0x5cc2, 0x75}, + {0x5cc3, 0x75}, + {0x5cc4, 0x75}, + {0x5cc5, 0x75}, + {0x5cc6, 0x75}, + {0x5cc7, 0x75}, + {0x5cc8, 0x75}, + {0x5cc9, 0x75}, + {0x5cca, 0x75}, + {0x5ccb, 0x75}, + {0x5ccc, 0x75}, + {0x5ccd, 0x75}, + {0x5cce, 0x75}, + {0x5ccf, 0x75}, + {0x5cd0, 0x75}, + {0x5cd1, 0x75}, + {0x5cd2, 0x75}, + {0x5cd3, 0x75}, + {0x5cd4, 0x75}, + {0x5cd5, 0x75}, + {0x5cd6, 0x75}, + {0x5cd7, 0x75}, + {0x5cd8, 0x75}, + {0x5cd9, 0x75}, + {0x5cda, 0x75}, + {0x5cdb, 0x75}, + {0x5cdc, 0x75}, + {0x5cdd, 0x75}, + {0x5cde, 0x75}, + {0x5cdf, 0x75}, + {0x5ce0, 0x75}, + {0x5ce1, 0x75}, + {0x5ce2, 0x75}, + {0x5ce3, 0x75}, + {0x5ce4, 0x75}, + {0x5ce5, 0x75}, + {0x5ce6, 0x75}, + {0x5ce7, 0x75}, + {0x5ce8, 0x75}, + {0x5ce9, 0x75}, + {0x5cea, 0x75}, + {0x5ceb, 0x75}, + {0x5cec, 0x75}, + {0x5ced, 0x75}, + {0x5cee, 0x75}, + {0x5cef, 0x75}, + {0x5cf0, 0x75}, + {0x5cf1, 0x75}, + {0x5cf2, 0x75}, + {0x5cf3, 0x75}, + {0x5cf4, 0x75}, + {0x5cf5, 0x75}, + {0x5cf6, 0x75}, + {0x5cf7, 0x75}, + {0x5cf8, 0x75}, + {0x5cf9, 0x75}, + {0x5cfa, 0x75}, + {0x5cfb, 0x75}, + {0x5cfc, 0x75}, + {0x5cfd, 0x75}, + {0x5cfe, 0x75}, + {0x5cff, 0x75}, + {0x5d00, 0x75}, + {0x5d01, 0x75}, + {0x5d02, 0x75}, + {0x5d03, 0x75}, + {0x5d04, 0x75}, + {0x5d05, 0x75}, + {0x5d06, 0x75}, + {0x5d07, 0x75}, + {0x5d08, 0x75}, + {0x5d09, 0x75}, + {0x5d0a, 0x75}, + {0x5d0b, 0x75}, + {0x5d0c, 0x75}, + {0x5d0d, 0x75}, + {0x5d0e, 0x75}, + {0x5d0f, 0x75}, + {0x5d10, 0x75}, + {0x5d11, 0x75}, + {0x5d12, 0x75}, + {0x5d13, 0x75}, + {0x5d14, 0x75}, + {0x5d15, 0x75}, + {0x5d16, 0x75}, + {0x5d17, 0x75}, + {0x5d18, 0x75}, + {0x5d19, 0x75}, + {0x5d1a, 0x75}, + {0x5d1b, 0x75}, + {0x5d1c, 0x75}, + {0x5d1d, 0x75}, + {0x5d1e, 0x75}, + {0x5d1f, 0x75}, + {0x5d20, 0x75}, + {0x5d21, 0x75}, + {0x5d22, 0x75}, + {0x5d23, 0x75}, + {0x5d24, 0x75}, + {0x5d25, 0x75}, + {0x5d26, 0x75}, + {0x5d27, 0x75}, + {0x5d28, 0x75}, + {0x5d29, 0x75}, + {0x5d2a, 0x75}, + {0x5d2b, 0x75}, + {0x5d2c, 0x75}, + {0x5d2d, 0x75}, + {0x5d2e, 0x75}, + {0x5d2f, 0x75}, + {0x5d30, 0x75}, + {0x5d31, 0x75}, + {0x5d32, 0x75}, + {0x5d33, 0x75}, + {0x5d34, 0x75}, + {0x5d35, 0x75}, + {0x5d36, 0x75}, + {0x5d37, 0x75}, + {0x5d38, 0x75}, + {0x5d39, 0x75}, + {0x5d3a, 0x75}, + {0x5d3b, 0x75}, + {0x5d3c, 0x75}, + {0x5d3d, 0x75}, + {0x5d3e, 0x75}, + {0x5d3f, 0x75}, + {0x5d40, 0x75}, + {0x5d41, 0x75}, + {0x5d42, 0x75}, + {0x5d43, 0x75}, + {0x5d44, 0x75}, + {0x5d45, 0x75}, + {0x5d46, 0x75}, + {0x5d47, 0x75}, + {0x5d48, 0x75}, + {0x5d49, 0x75}, + {0x5d4a, 0x75}, + {0x5d4b, 0x75}, + {0x5d4c, 0x75}, + {0x5d4d, 0x75}, + {0x5d4e, 0x75}, + {0x5d4f, 0x75}, + {0x5d50, 0x75}, + {0x5d51, 0x75}, + {0x5d52, 0x75}, + {0x5d53, 0x75}, + {0x5d54, 0x75}, + {0x5d55, 0x75}, + {0x5d56, 0x75}, + {0x5d57, 0x75}, + {0x5d58, 0x75}, + {0x5d59, 0x75}, + {0x5d5a, 0x75}, + {0x5d5b, 0x75}, + {0x5d5c, 0x75}, + {0x5d5d, 0x75}, + {0x5d5e, 0x75}, + {0x5d5f, 0x75}, + {0x5d60, 0x75}, + {0x5d61, 0x75}, + {0x5d62, 0x75}, + {0x5d63, 0x75}, + {0x5d64, 0x75}, + {0x5d65, 0x75}, + {0x5d66, 0x75}, + {0x5d67, 0x75}, + {0x5d68, 0x75}, + {0x5d69, 0x75}, + {0x5d6a, 0x75}, + {0x5d6b, 0x75}, + {0x5d6c, 0x75}, + {0x5d6d, 0x75}, + {0x5d6e, 0x75}, + {0x5d6f, 0x75}, + {0x5d70, 0x75}, + {0x5d71, 0x75}, + {0x5d72, 0x75}, + {0x5d73, 0x75}, + {0x5d74, 0x75}, + {0x5d75, 0x75}, + {0x5d76, 0x75}, + {0x5d77, 0x75}, + {0x5d78, 0x75}, + {0x5d79, 0x75}, + {0x5d7a, 0x75}, + {0x5d7b, 0x75}, + {0x5d7c, 0x75}, + {0x5d7d, 0x75}, + {0x5d7e, 0x75}, + {0x5d7f, 0x75}, + {0x5d80, 0x75}, + {0x5d81, 0x75}, + {0x5d82, 0x75}, + {0x5d83, 0x75}, + {0x5d84, 0x75}, + {0x5d85, 0x75}, + {0x5d86, 0x75}, + {0x5d87, 0x75}, + {0x5d88, 0x75}, + {0x5d89, 0x75}, + {0x5d8a, 0x75}, + {0x5d8b, 0x75}, + {0x5d8c, 0x75}, + {0x5d8d, 0x75}, + {0x5d8e, 0x75}, + {0x5d8f, 0x75}, + {0x5d90, 0x75}, + {0x5d91, 0x75}, + {0x5d92, 0x75}, + {0x5d93, 0x75}, + {0x5d94, 0x75}, + {0x5d95, 0x75}, + {0x5d96, 0x75}, + {0x5d97, 0x75}, + {0x5d98, 0x75}, + {0x5d99, 0x75}, + {0x5d9a, 0x75}, + {0x5d9b, 0x75}, + {0x5d9c, 0x75}, + {0x5d9d, 0x75}, + {0x5d9e, 0x75}, + {0x5d9f, 0x75}, + {0x5da0, 0x75}, + {0x5da1, 0x75}, + {0x5da2, 0x75}, + {0x5da3, 0x75}, + {0x5da4, 0x75}, + {0x5da5, 0x75}, + {0x5da6, 0x75}, + {0x5da7, 0x75}, + {0x5da8, 0x75}, + {0x5da9, 0x75}, + {0x5daa, 0x75}, + {0x5dab, 0x75}, + {0x5dac, 0x75}, + {0x5dad, 0x75}, + {0x5dae, 0x75}, + {0x5daf, 0x75}, + {0x5db0, 0x75}, + {0x5db1, 0x75}, + {0x5db2, 0x75}, + {0x5db3, 0x75}, + {0x5db4, 0x75}, + {0x5db5, 0x75}, + {0x5db6, 0x75}, + {0x5db7, 0x75}, + {0x5db8, 0x75}, + {0x5db9, 0x75}, + {0x5dba, 0x75}, + {0x5dbb, 0x75}, + {0x5dbc, 0x75}, + {0x5dbd, 0x75}, + {0x5dbe, 0x75}, + {0x5dbf, 0x75}, + {0x5dc0, 0x75}, + {0x5dc1, 0x75}, + {0x5dc2, 0x75}, + {0x5dc3, 0x75}, + {0x5dc4, 0x75}, + {0x5dc5, 0x75}, + {0x5dc6, 0x75}, + {0x5dc7, 0x75}, + {0x5dc8, 0x75}, + {0x5dc9, 0x75}, + {0x5dca, 0x75}, + {0x5dcb, 0x75}, + {0x5dcc, 0x75}, + {0x5dcd, 0x75}, + {0x5dce, 0x75}, + {0x5dcf, 0x75}, + {0x5dd0, 0x75}, + {0x5dd1, 0x75}, + {0x5dd2, 0x75}, + {0x5dd3, 0x75}, + {0x5dd4, 0x75}, + {0x5dd5, 0x75}, + {0x5dd6, 0x75}, + {0x5dd7, 0x75}, + {0x5dd8, 0x75}, + {0x5dd9, 0x75}, + {0x5dda, 0x75}, + {0x5ddb, 0x75}, + {0x5ddc, 0x75}, + {0x5ddd, 0x75}, + {0x5dde, 0x75}, + {0x5ddf, 0x75}, + {0x5de0, 0x75}, + {0x5de1, 0x75}, + {0x5de2, 0x75}, + {0x5de3, 0x75}, + {0x5de4, 0x75}, + {0x5de5, 0x75}, + {0x5de6, 0x75}, + {0x5de7, 0x75}, + {0x5de8, 0x75}, + {0x5de9, 0x75}, + {0x5dea, 0x75}, + {0x5deb, 0x75}, + {0x5dec, 0x75}, + {0x5ded, 0x75}, + {0x5dee, 0x75}, + {0x5def, 0x75}, + {0x5df0, 0x75}, + {0x5df1, 0x75}, + {0x5df2, 0x75}, + {0x5df3, 0x75}, + {0x5df4, 0x75}, + {0x5df5, 0x75}, + {0x5df6, 0x75}, + {0x5df7, 0x75}, + {0x5df8, 0x75}, + {0x5df9, 0x75}, + {0x5dfa, 0x75}, + {0x5dfb, 0x75}, + {0x5dfc, 0x75}, + {0x5dfd, 0x75}, + {0x5dfe, 0x75}, + {0x5dff, 0x75}, + {0x5e00, 0x75}, + {0x5e01, 0x75}, + {0x5e02, 0x75}, + {0x5e03, 0x75}, + {0x5e04, 0x75}, + {0x5e05, 0x75}, + {0x5e06, 0x75}, + {0x5e07, 0x75}, + {0x5e08, 0x75}, + {0x5e09, 0x75}, + {0x5e0a, 0x75}, + {0x5e0b, 0x75}, + {0x5e0c, 0x75}, + {0x5e0d, 0x75}, + {0x5e0e, 0x75}, + {0x5e0f, 0x75}, + {0x5e10, 0x75}, + {0x5e11, 0x75}, + {0x5e12, 0x75}, + {0x5e13, 0x75}, + {0x5e14, 0x75}, + {0x5e15, 0x75}, + {0x5e16, 0x75}, + {0x5e17, 0x75}, + {0x5e18, 0x75}, + {0x5e19, 0x75}, + {0x5e1a, 0x75}, + {0x5e1b, 0x75}, + {0x5e1c, 0x75}, + {0x5e1d, 0x75}, + {0x5e1e, 0x75}, + {0x5e1f, 0x75}, + {0x5e20, 0x75}, + {0x5e21, 0x75}, + {0x5e22, 0x75}, + {0x5e23, 0x75}, + {0x5e24, 0x75}, + {0x5e25, 0x75}, + {0x5e26, 0x75}, + {0x5e27, 0x75}, + {0x5e28, 0x75}, + {0x5e29, 0x75}, + {0x5e2a, 0x75}, + {0x5e2b, 0x75}, + {0x5e2c, 0x75}, + {0x5e2d, 0x75}, + {0x5e2e, 0x75}, + {0x5e2f, 0x75}, + {0x5e30, 0x75}, + {0x5e31, 0x75}, + {0x5e32, 0x75}, + {0x5e33, 0x75}, + {0x5e34, 0x75}, + {0x5e35, 0x75}, + {0x5e36, 0x75}, + {0x5e37, 0x75}, + {0x5e38, 0x75}, + {0x5e39, 0x75}, + {0x5e3a, 0x75}, + {0x5e3b, 0x75}, + {0x5e3c, 0x75}, + {0x5e3d, 0x75}, + {0x5e3e, 0x75}, + {0x5e3f, 0x75}, + {0x5e40, 0x75}, + {0x5e41, 0x75}, + {0x5e42, 0x75}, + {0x5e43, 0x75}, + {0x5e44, 0x75}, + {0x5e45, 0x75}, + {0x5e46, 0x75}, + {0x5e47, 0x75}, + {0x5e48, 0x75}, + {0x5e49, 0x75}, + {0x5e4a, 0x75}, + {0x5e4b, 0x75}, + {0x5e4c, 0x75}, + {0x5e4d, 0x75}, + {0x5e4e, 0x75}, + {0x5e4f, 0x75}, + {0x5e50, 0x75}, + {0x5e51, 0x75}, + {0x5e52, 0x75}, + {0x5e53, 0x75}, + {0x5e54, 0x75}, + {0x5e55, 0x75}, + {0x5e56, 0x75}, + {0x5e57, 0x75}, + {0x5e58, 0x75}, + {0x5e59, 0x75}, + {0x5e5a, 0x75}, + {0x5e5b, 0x75}, + {0x5e5c, 0x75}, + {0x5e5d, 0x75}, + {0x5e5e, 0x75}, + {0x5e5f, 0x75}, + {0x5e60, 0x75}, + {0x5e61, 0x75}, + {0x5e62, 0x75}, + {0x5e63, 0x75}, + {0x5e64, 0x75}, + {0x5e65, 0x75}, + {0x5e66, 0x75}, + {0x5e67, 0x75}, + {0x5e68, 0x75}, + {0x5e69, 0x75}, + {0x5e6a, 0x75}, + {0x5e6b, 0x75}, + {0x5e6c, 0x75}, + {0x5e6d, 0x75}, + {0x5e6e, 0x75}, + {0x5e6f, 0x75}, + {0x5e70, 0x75}, + {0x5e71, 0x75}, + {0x5e72, 0x75}, + {0x5e73, 0x75}, + {0x5e74, 0x75}, + {0x5e75, 0x75}, + {0x5e76, 0x75}, + {0x5e77, 0x75}, + {0x5e78, 0x75}, + {0x5e79, 0x75}, + {0x5e7a, 0x75}, + {0x5e7b, 0x75}, + {0x5e7c, 0x75}, + {0x5e7d, 0x75}, + {0x5e7e, 0x75}, + {0x5e7f, 0x75}, + {0x5e80, 0x75}, + {0x5e81, 0x75}, + {0x5e82, 0x75}, + {0x5e83, 0x75}, + {0x5e84, 0x75}, + {0x5e85, 0x75}, + {0x5e86, 0x75}, + {0x5e87, 0x75}, + {0x5e88, 0x75}, + {0x5e89, 0x75}, + {0x5e8a, 0x75}, + {0x5e8b, 0x75}, + {0x5e8c, 0x75}, + {0x5e8d, 0x75}, + {0x5e8e, 0x75}, + {0x5e8f, 0x75}, + {0x5e90, 0x75}, + {0x5e91, 0x75}, + {0x5e92, 0x75}, + {0x5e93, 0x75}, + {0x5e94, 0x75}, + {0x5e95, 0x75}, + {0x5e96, 0x75}, + {0x5e97, 0x75}, + {0x5e98, 0x75}, + {0x5e99, 0x75}, + {0x5e9a, 0x75}, + {0x5e9b, 0x75}, + {0x5e9c, 0x75}, + {0x5e9d, 0x75}, + {0x5e9e, 0x75}, + {0x5e9f, 0x75}, + {0x5ea0, 0x75}, + {0x5ea1, 0x75}, + {0x5ea2, 0x75}, + {0x5ea3, 0x75}, + {0x5ea4, 0x75}, + {0x5ea5, 0x75}, + {0x5ea6, 0x75}, + {0x5ea7, 0x75}, + {0x5ea8, 0x75}, + {0x5ea9, 0x75}, + {0x5eaa, 0x75}, + {0x5eab, 0x75}, + {0x5eac, 0x75}, + {0x5ead, 0x75}, + {0x5eae, 0x75}, + {0x5eaf, 0x75}, + {0x5eb0, 0x75}, + {0x5eb1, 0x75}, + {0x5eb2, 0x75}, + {0x5eb3, 0x75}, + {0x5eb4, 0x75}, + {0x5eb5, 0x75}, + {0x5eb6, 0x75}, + {0x5eb7, 0x75}, + {0x5eb8, 0x75}, + {0x5eb9, 0x75}, + {0x5eba, 0x75}, + {0x5ebb, 0x75}, + {0x5ebc, 0x75}, + {0x5ebd, 0x75}, + {0x5ebe, 0x75}, + {0x5ebf, 0x75}, + {0x5ec0, 0x75}, + {0x5ec1, 0x75}, + {0x5ec2, 0x75}, + {0x5ec3, 0x75}, + {0x5ec4, 0x75}, + {0x5ec5, 0x75}, + {0x5ec6, 0x75}, + {0x5ec7, 0x75}, + {0x5ec8, 0x75}, + {0x5ec9, 0x75}, + {0x5eca, 0x75}, + {0x5ecb, 0x75}, + {0x5ecc, 0x75}, + {0x5ecd, 0x75}, + {0x5ece, 0x75}, + {0x5ecf, 0x75}, + {0x5ed0, 0x75}, + {0x5ed1, 0x75}, + {0x5ed2, 0x75}, + {0x5ed3, 0x75}, + {0x5ed4, 0x75}, + {0x5ed5, 0x75}, + {0x5ed6, 0x75}, + {0x5ed7, 0x75}, + {0x5ed8, 0x75}, + {0x5ed9, 0x75}, + {0x5eda, 0x75}, + {0x5edb, 0x75}, + {0x5edc, 0x75}, + {0x5edd, 0x75}, + {0x5ede, 0x75}, + {0x5edf, 0x75}, + {0x5ee0, 0x75}, + {0x5ee1, 0x75}, + {0x5ee2, 0x75}, + {0x5ee3, 0x75}, + {0x5ee4, 0x75}, + {0x5ee5, 0x75}, + {0x5ee6, 0x75}, + {0x5ee7, 0x75}, + {0x5ee8, 0x75}, + {0x5ee9, 0x75}, + {0x5eea, 0x75}, + {0x5eeb, 0x75}, + {0x5eec, 0x75}, + {0x5eed, 0x75}, + {0x5eee, 0x75}, + {0x5eef, 0x75}, + {0x5ef0, 0x75}, + {0x5ef1, 0x75}, + {0x5ef2, 0x75}, + {0x5ef3, 0x75}, + {0x5ef4, 0x75}, + {0x5ef5, 0x75}, + {0x5ef6, 0x75}, + {0x5ef7, 0x75}, + {0x5ef8, 0x75}, + {0x5ef9, 0x75}, + {0x5efa, 0x75}, + {0x5efb, 0x75}, + {0x5efc, 0x75}, + {0x5efd, 0x75}, + {0x5efe, 0x75}, + {0x5eff, 0x75}, + {0x5f00, 0x75}, + {0x5f01, 0x75}, + {0x5f02, 0x75}, + {0x5f03, 0x75}, + {0x5f04, 0x75}, + {0x5f05, 0x75}, + {0x5f06, 0x75}, + {0x5f07, 0x75}, + {0x5f08, 0x75}, + {0x5f09, 0x75}, + {0x5f0a, 0x75}, + {0x5f0b, 0x75}, + {0x5f0c, 0x75}, + {0x5f0d, 0x75}, + {0x5f0e, 0x75}, + {0x5f0f, 0x75}, + {0x5f10, 0x75}, + {0x5f11, 0x75}, + {0x5f12, 0x75}, + {0x5f13, 0x75}, + {0x5f14, 0x75}, + {0x5f15, 0x75}, + {0x5f16, 0x75}, + {0x5f17, 0x75}, + {0x5f18, 0x75}, + {0x5f19, 0x75}, + {0x5f1a, 0x75}, + {0x5f1b, 0x75}, + {0x5f1c, 0x75}, + {0x5f1d, 0x75}, + {0x5f1e, 0x75}, + {0x5f1f, 0x75}, +}; + +static const struct ov08x40_reg mode_1928x1208_regs[] = { + {0x5000, 0x55}, + {0x5001, 0x00}, + {0x5008, 0xb0}, + {0x50c1, 0x00}, + {0x53c1, 0x00}, + {0x5f40, 0x00}, + {0x5f41, 0x40}, + {0x0300, 0x3a}, + {0x0301, 0xc8}, + {0x0302, 0x31}, + {0x0303, 0x03}, + {0x0304, 0x01}, + {0x0305, 0xa1}, + {0x0306, 0x04}, + {0x0307, 0x01}, + {0x0308, 0x03}, + {0x0309, 0x03}, + {0x0310, 0x0a}, + {0x0311, 0x02}, + {0x0312, 0x01}, + {0x0313, 0x08}, + {0x0314, 0x66}, + {0x0315, 0x00}, + {0x0316, 0x34}, + {0x0320, 0x02}, + {0x0321, 0x03}, + {0x0323, 0x05}, + {0x0324, 0x01}, + {0x0325, 0xb8}, + {0x0326, 0x4a}, + {0x0327, 0x04}, + {0x0329, 0x00}, + {0x032a, 0x05}, + {0x032b, 0x00}, + {0x032c, 0x00}, + {0x032d, 0x00}, + {0x032e, 0x02}, + {0x032f, 0xa0}, + {0x0350, 0x00}, + {0x0360, 0x01}, + {0x1216, 0x60}, + {0x1217, 0x5b}, + {0x1218, 0x00}, + {0x1220, 0x24}, + {0x198a, 0x00}, + {0x198b, 0x01}, + {0x198e, 0x00}, + {0x198f, 0x01}, + {0x3009, 0x04}, + {0x3012, 0x41}, + {0x3015, 0x00}, + {0x3016, 0xb0}, + {0x3017, 0xf0}, + {0x3018, 0xf0}, + {0x3019, 0xd2}, + {0x301a, 0xb0}, + {0x301c, 0x81}, + {0x301d, 0x02}, + {0x301e, 0x80}, + {0x3022, 0xf0}, + {0x3025, 0x89}, + {0x3030, 0x03}, + {0x3044, 0xc2}, + {0x3050, 0x35}, + {0x3051, 0x60}, + {0x3052, 0x25}, + {0x3053, 0x00}, + {0x3054, 0x00}, + {0x3055, 0x02}, + {0x3056, 0x80}, + {0x3057, 0x80}, + {0x3058, 0x80}, + {0x3059, 0x00}, + {0x3107, 0x86}, + {0x3400, 0x1c}, + {0x3401, 0x80}, + {0x3402, 0x8c}, + {0x3419, 0x08}, + {0x341a, 0xaf}, + {0x341b, 0x30}, + {0x3420, 0x00}, + {0x3421, 0x00}, + {0x3422, 0x00}, + {0x3423, 0x00}, + {0x3424, 0x00}, + {0x3425, 0x00}, + {0x3426, 0x00}, + {0x3427, 0x00}, + {0x3428, 0x0f}, + {0x3429, 0x00}, + {0x342a, 0x00}, + {0x342b, 0x00}, + {0x342c, 0x00}, + {0x342d, 0x00}, + {0x342e, 0x00}, + {0x342f, 0x11}, + {0x3430, 0x11}, + {0x3431, 0x10}, + {0x3432, 0x00}, + {0x3433, 0x00}, + {0x3434, 0x00}, + {0x3435, 0x00}, + {0x3436, 0x00}, + {0x3437, 0x00}, + {0x3442, 0x02}, + {0x3443, 0x02}, + {0x3444, 0x07}, + {0x3450, 0x00}, + {0x3451, 0x00}, + {0x3452, 0x18}, + {0x3453, 0x18}, + {0x3454, 0x00}, + {0x3455, 0x80}, + {0x3456, 0x08}, + {0x3500, 0x00}, + {0x3501, 0x02}, + {0x3502, 0x00}, + {0x3504, 0x4c}, + {0x3506, 0x30}, + {0x3507, 0x00}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x01}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x3540, 0x00}, + {0x3541, 0x01}, + {0x3542, 0x00}, + {0x3544, 0x4c}, + {0x3546, 0x30}, + {0x3547, 0x00}, + {0x3548, 0x01}, + {0x3549, 0x00}, + {0x354a, 0x01}, + {0x354b, 0x00}, + {0x354c, 0x00}, + {0x3688, 0x02}, + {0x368a, 0x2e}, + {0x368e, 0x71}, + {0x3696, 0xd1}, + {0x3699, 0x00}, + {0x369a, 0x00}, + {0x36a4, 0x00}, + {0x36a6, 0x00}, + {0x3711, 0x00}, + {0x3712, 0x50}, + {0x3713, 0x00}, + {0x3714, 0x21}, + {0x3716, 0x00}, + {0x3718, 0x07}, + {0x371a, 0x1c}, + {0x371b, 0x00}, + {0x3720, 0x08}, + {0x3725, 0x32}, + {0x3727, 0x05}, + {0x3760, 0x02}, + {0x3761, 0x28}, + {0x3762, 0x02}, + {0x3763, 0x02}, + {0x3764, 0x02}, + {0x3765, 0x2c}, + {0x3766, 0x04}, + {0x3767, 0x2c}, + {0x3768, 0x02}, + {0x3769, 0x00}, + {0x376b, 0x20}, + {0x376e, 0x07}, + {0x37b0, 0x01}, + {0x37b1, 0x0f}, + {0x37b2, 0x01}, + {0x37b3, 0xd6}, + {0x37b4, 0x01}, + {0x37b5, 0x48}, + {0x37b6, 0x02}, + {0x37b7, 0x40}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0f}, + {0x3805, 0x1f}, + {0x3806, 0x09}, + {0x3807, 0x7f}, + {0x3808, 0x07}, + {0x3809, 0x88}, + {0x380a, 0x04}, + {0x380b, 0xb8}, + {0x380c, 0x02}, + {0x380d, 0xd0}, + {0x380e, 0x11}, + {0x380f, 0x5c}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3812, 0x00}, + {0x3813, 0x03}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x02}, + {0x3821, 0x14}, + {0x3822, 0x00}, + {0x3823, 0x04}, + {0x3828, 0x0f}, + {0x382a, 0x80}, + {0x382e, 0x41}, + {0x3837, 0x08}, + {0x383a, 0x81}, + {0x383b, 0x81}, + {0x383c, 0x11}, + {0x383d, 0x11}, + {0x383e, 0x00}, + {0x383f, 0x38}, + {0x3840, 0x00}, + {0x3847, 0x00}, + {0x384a, 0x00}, + {0x384c, 0x02}, + {0x384d, 0xd0}, + {0x3856, 0x50}, + {0x3857, 0x30}, + {0x3858, 0x80}, + {0x3859, 0x40}, + {0x3860, 0x00}, + {0x3888, 0x00}, + {0x3889, 0x00}, + {0x388a, 0x00}, + {0x388b, 0x00}, + {0x388c, 0x00}, + {0x388d, 0x00}, + {0x388e, 0x00}, + {0x388f, 0x00}, + {0x3894, 0x00}, + {0x3895, 0x00}, + {0x3c84, 0x00}, + {0x3d85, 0x8b}, + {0x3daa, 0x80}, + {0x3dab, 0x14}, + {0x3dac, 0x80}, + {0x3dad, 0xc8}, + {0x3dae, 0x81}, + {0x3daf, 0x7b}, + {0x3f00, 0x10}, + {0x3f01, 0x11}, + {0x3f06, 0x0d}, + {0x3f07, 0x0b}, + {0x3f08, 0x0d}, + {0x3f09, 0x0b}, + {0x3f0a, 0x01}, + {0x3f0b, 0x11}, + {0x3f0c, 0x33}, + {0x4001, 0x07}, + {0x4007, 0x20}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x04}, + {0x400c, 0x00}, + {0x400d, 0x04}, + {0x400e, 0x14}, + {0x4010, 0xf4}, + {0x4011, 0x03}, + {0x4012, 0x55}, + {0x4015, 0x00}, + {0x4016, 0x27}, + {0x4017, 0x00}, + {0x4018, 0x0f}, + {0x401b, 0x08}, + {0x401c, 0x00}, + {0x401d, 0x10}, + {0x401e, 0x02}, + {0x401f, 0x00}, + {0x4050, 0x06}, + {0x4051, 0xff}, + {0x4052, 0xff}, + {0x4053, 0xff}, + {0x4054, 0xff}, + {0x4055, 0xff}, + {0x4056, 0xff}, + {0x4057, 0x7f}, + {0x4058, 0x00}, + {0x4059, 0x00}, + {0x405a, 0x00}, + {0x405b, 0x00}, + {0x405c, 0x07}, + {0x405d, 0xff}, + {0x405e, 0x07}, + {0x405f, 0xff}, + {0x4080, 0x78}, + {0x4081, 0x78}, + {0x4082, 0x78}, + {0x4083, 0x78}, + {0x4019, 0x00}, + {0x401a, 0x40}, + {0x4020, 0x04}, + {0x4021, 0x00}, + {0x4022, 0x04}, + {0x4023, 0x00}, + {0x4024, 0x04}, + {0x4025, 0x00}, + {0x4026, 0x04}, + {0x4027, 0x00}, + {0x4030, 0x00}, + {0x4031, 0x00}, + {0x4032, 0x00}, + {0x4033, 0x00}, + {0x4034, 0x00}, + {0x4035, 0x00}, + {0x4036, 0x00}, + {0x4037, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x80}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4060, 0x00}, + {0x4061, 0x00}, + {0x4062, 0x00}, + {0x4063, 0x00}, + {0x4064, 0x00}, + {0x4065, 0x00}, + {0x4066, 0x00}, + {0x4067, 0x00}, + {0x4068, 0x00}, + {0x4069, 0x00}, + {0x406a, 0x00}, + {0x406b, 0x00}, + {0x406c, 0x00}, + {0x406d, 0x00}, + {0x406e, 0x00}, + {0x406f, 0x00}, + {0x4070, 0x00}, + {0x4071, 0x00}, + {0x4072, 0x00}, + {0x4073, 0x00}, + {0x4074, 0x00}, + {0x4075, 0x00}, + {0x4076, 0x00}, + {0x4077, 0x00}, + {0x4078, 0x00}, + {0x4079, 0x00}, + {0x407a, 0x00}, + {0x407b, 0x00}, + {0x407c, 0x00}, + {0x407d, 0x00}, + {0x407e, 0x00}, + {0x407f, 0x00}, + {0x40e0, 0x00}, + {0x40e1, 0x00}, + {0x40e2, 0x00}, + {0x40e3, 0x00}, + {0x40e4, 0x00}, + {0x40e5, 0x00}, + {0x40e6, 0x00}, + {0x40e7, 0x00}, + {0x40e8, 0x00}, + {0x40e9, 0x80}, + {0x40ea, 0x00}, + {0x40eb, 0x80}, + {0x40ec, 0x00}, + {0x40ed, 0x80}, + {0x40ee, 0x00}, + {0x40ef, 0x80}, + {0x40f0, 0x02}, + {0x40f1, 0x04}, + {0x4300, 0x00}, + {0x4301, 0x00}, + {0x4302, 0x00}, + {0x4303, 0x00}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4307, 0x00}, + {0x4308, 0x00}, + {0x4309, 0x00}, + {0x430a, 0x00}, + {0x430b, 0xff}, + {0x430c, 0xff}, + {0x430d, 0x00}, + {0x430e, 0x00}, + {0x4315, 0x00}, + {0x4316, 0x00}, + {0x4317, 0x00}, + {0x4318, 0x00}, + {0x4319, 0x00}, + {0x431a, 0x00}, + {0x431b, 0x00}, + {0x431c, 0x00}, + {0x4500, 0x07}, + {0x4501, 0x10}, + {0x4502, 0x00}, + {0x4503, 0x0f}, + {0x4504, 0x80}, + {0x4506, 0x01}, + {0x4509, 0x05}, + {0x450c, 0x00}, + {0x450d, 0x20}, + {0x450e, 0x00}, + {0x450f, 0x00}, + {0x4510, 0x00}, + {0x4523, 0x00}, + {0x4526, 0x00}, + {0x4542, 0x00}, + {0x4543, 0x00}, + {0x4544, 0x00}, + {0x4545, 0x00}, + {0x4546, 0x00}, + {0x4547, 0x10}, + {0x4602, 0x00}, + {0x4603, 0x15}, + {0x460b, 0x07}, + {0x4680, 0x11}, + {0x4686, 0x00}, + {0x4687, 0x00}, + {0x4700, 0x00}, + {0x4800, 0x64}, + {0x4806, 0x40}, + {0x480b, 0x10}, + {0x480c, 0x80}, + {0x480f, 0x32}, + {0x4813, 0xe4}, + {0x4837, 0x14}, + {0x4850, 0x42}, + {0x4884, 0x04}, + {0x4c00, 0xf8}, + {0x4c01, 0x44}, + {0x4c03, 0x00}, + {0x4d00, 0x00}, + {0x4d01, 0x16}, + {0x4d04, 0x10}, + {0x4d05, 0x00}, + {0x4d06, 0x0c}, + {0x4d07, 0x00}, + {0x3d84, 0x04}, + {0x3680, 0xa4}, + {0x3682, 0x80}, + {0x3601, 0x40}, + {0x3602, 0x90}, + {0x3608, 0x0a}, + {0x3938, 0x09}, + {0x3a74, 0x84}, + {0x3a99, 0x84}, + {0x3ab9, 0xa6}, + {0x3aba, 0xba}, + {0x3b12, 0x84}, + {0x3b14, 0xbb}, + {0x3b15, 0xbf}, + {0x3a29, 0x26}, + {0x3a1f, 0x8a}, + {0x3a22, 0x91}, + {0x3a25, 0x96}, + {0x3a28, 0xb4}, + {0x3a2b, 0xba}, + {0x3a2e, 0xbf}, + {0x3a31, 0xc1}, + {0x3a20, 0x05}, + {0x3939, 0x6b}, + {0x3902, 0x10}, + {0x3903, 0x10}, + {0x3904, 0x10}, + {0x3905, 0x10}, + {0x3906, 0x01}, + {0x3907, 0x0b}, + {0x3908, 0x10}, + {0x3909, 0x13}, + {0x360f, 0x99}, + {0x390b, 0x11}, + {0x390c, 0x21}, + {0x390d, 0x32}, + {0x390e, 0x76}, + {0x3911, 0x90}, + {0x3913, 0x90}, + {0x3b3f, 0x9d}, + {0x3b45, 0x9d}, + {0x3b1b, 0xc9}, + {0x3b21, 0xc9}, + {0x3a1a, 0x1c}, + {0x3a23, 0x15}, + {0x3a26, 0x17}, + {0x3a2c, 0x50}, + {0x3a2f, 0x18}, + {0x3a32, 0x4f}, + {0x3ace, 0x01}, + {0x3ad2, 0x01}, + {0x3ad6, 0x01}, + {0x3ada, 0x01}, + {0x3ade, 0x01}, + {0x3ae2, 0x01}, + {0x3aee, 0x01}, + {0x3af2, 0x01}, + {0x3af6, 0x01}, + {0x3afa, 0x01}, + {0x3afe, 0x01}, + {0x3b02, 0x01}, + {0x3b06, 0x01}, + {0x3b0a, 0x01}, + {0x3b0b, 0x00}, + {0x3b0e, 0x01}, + {0x3b0f, 0x00}, + {0x392c, 0x02}, + {0x392d, 0x01}, + {0x392e, 0x04}, + {0x392f, 0x03}, + {0x3930, 0x09}, + {0x3931, 0x07}, + {0x3932, 0x10}, + {0x3933, 0x0d}, + {0x3609, 0x08}, + {0x3921, 0x0f}, + {0x3928, 0x15}, + {0x3929, 0x2a}, + {0x392a, 0x52}, + {0x392b, 0xa3}, + {0x340b, 0x1b}, + {0x3426, 0x10}, + {0x3407, 0x01}, + {0x3404, 0x01}, + {0x3500, 0x00}, + {0x3501, 0x08}, + {0x3502, 0x10}, + {0x3508, 0x04}, + {0x3509, 0x00}, +}; + +static const char * const ov08x40_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Configurations for supported link frequencies */ +#define OV08X40_LINK_FREQ_400MHZ 400000000ULL + +#define OV08X40_EXT_CLK 19200000 +#define OV08X40_DATA_LANES 4 + +/* + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample + * data rate => double data rate; number of lanes => 4; bits per pixel => 10 + */ +static u64 link_freq_to_pixel_rate(u64 f) +{ + f *= 2 * OV08X40_DATA_LANES; + do_div(f, 10); + + return f; +} + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[] = { + OV08X40_LINK_FREQ_400MHZ, +}; + +/* Link frequency configs */ +static const struct ov08x40_link_freq_config link_freq_configs[] = { + [OV08X40_LINK_FREQ_400MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_800mbps), + .regs = mipi_data_rate_800mbps, + } + }, +}; + +/* Mode configs */ +static const struct ov08x40_mode supported_modes[] = { + { + .width = 3856, + .height = 2416, + .vts_def = OV08X40_VTS_30FPS, + .vts_min = OV08X40_VTS_30FPS, + .lanes = 4, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3856x2416_regs), + .regs = mode_3856x2416_regs, + }, + .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX, + }, + { + .width = 1928, + .height = 1208, + .vts_def = OV08X40_VTS_BIN_30FPS, + .vts_min = OV08X40_VTS_BIN_30FPS, + .lanes = 4, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1928x1208_regs), + .regs = mode_1928x1208_regs, + }, + .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX, + }, +}; + +struct ov08x40 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov08x40_mode *cur_mode; + + /* Mutex for serialized access */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +#define to_ov08x40(_sd) container_of(_sd, struct ov08x40, sd) + +/* Read registers up to 4 at a time */ +static int ov08x40_read_reg(struct ov08x40 *ov08x, + u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + struct i2c_msg msgs[2]; + u8 *data_be_p; + int ret; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + + if (len > 4) + 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 *)®_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; +} + +/* Write registers up to 4 at a time */ +static int ov08x40_write_reg(struct ov08x40 *ov08x, + u16 reg, u32 len, u32 __val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int buf_i, val_i; + u8 buf[6], *val_p; + __be32 val; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val = cpu_to_be32(__val); + val_p = (u8 *)&val; + 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; +} + +/* Write a list of registers */ +static int ov08x40_write_regs(struct ov08x40 *ov08x, + const struct ov08x40_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = ov08x40_write_reg(ov08x, regs[i].address, 1, + regs[i].val); + + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +static int ov08x40_write_reg_list(struct ov08x40 *ov08x, + const struct ov08x40_reg_list *r_list) +{ + return ov08x40_write_regs(ov08x, r_list->regs, r_list->num_of_regs); +} + +static int ov08x40_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + const struct ov08x40_mode *default_mode = &supported_modes[0]; + struct ov08x40 *ov08x = to_ov08x40(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->state, 0); + + mutex_lock(&ov08x->mutex); + + /* Initialize try_fmt */ + try_fmt->width = default_mode->width; + try_fmt->height = default_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + /* No crop or compose */ + mutex_unlock(&ov08x->mutex); + + return 0; +} + +static int ov08x40_update_digital_gain(struct ov08x40 *ov08x, u32 d_gain) +{ + int ret; + u32 val; + + /* + * 0x350C[1:0], 0x350B[7:0], 0x350A[4:0] + */ + + val = (d_gain & OV08X40_DGTL_GAIN_L_MASK) << OV08X40_DGTL_GAIN_L_SHIFT; + ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_L, + OV08X40_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV08X40_DGTL_GAIN_M_SHIFT) & OV08X40_DGTL_GAIN_M_MASK; + ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_M, + OV08X40_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV08X40_DGTL_GAIN_H_SHIFT) & OV08X40_DGTL_GAIN_H_MASK; + + return ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_H, + OV08X40_REG_VALUE_08BIT, val); +} + +static int ov08x40_enable_test_pattern(struct ov08x40 *ov08x, u32 pattern) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + if (pattern) { + ret = ov08x40_read_reg(ov08x, OV08X40_REG_ISP, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov08x40_write_reg(ov08x, OV08X40_REG_ISP, + OV08X40_REG_VALUE_08BIT, + val | BIT(1)); + if (ret) + return ret; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov08x40_write_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, + val | BIT(0)); + if (ret) + return ret; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + val &= OV08X40_TEST_PATTERN_MASK; + val |= ((pattern - 1) << OV08X40_TEST_PATTERN_BAR_SHIFT) | + OV08X40_TEST_PATTERN_ENABLE; + } else { + val &= ~OV08X40_TEST_PATTERN_ENABLE; + } + + return ov08x40_write_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, val); +} + +static int ov08x40_set_ctrl_hflip(struct ov08x40 *ov08x, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_MIRROR, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_MIRROR, + OV08X40_REG_VALUE_08BIT, + ctrl_val ? val | BIT(2) : val & ~BIT(2)); +} + +static int ov08x40_set_ctrl_vflip(struct ov08x40 *ov08x, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_VFLIP, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_VFLIP, + OV08X40_REG_VALUE_08BIT, + ctrl_val ? val | BIT(2) : val & ~BIT(2)); +} + +static int ov08x40_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov08x40 *ov08x = container_of(ctrl->handler, + struct ov08x40, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + s64 max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov08x->cur_mode->height + ctrl->val - OV08X40_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(ov08x->exposure, + ov08x->exposure->minimum, + max, ov08x->exposure->step, max); + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_ANALOG_GAIN, + OV08X40_REG_VALUE_16BIT, + ctrl->val << 1); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov08x40_update_digital_gain(ov08x, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_EXPOSURE, + OV08X40_REG_VALUE_24BIT, + ctrl->val); + break; + case V4L2_CID_VBLANK: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_VTS, + OV08X40_REG_VALUE_16BIT, + ov08x->cur_mode->height + + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov08x40_enable_test_pattern(ov08x, ctrl->val); + break; + case V4L2_CID_HFLIP: + ov08x40_set_ctrl_hflip(ov08x, ctrl->val); + break; + case V4L2_CID_VFLIP: + ov08x40_set_ctrl_vflip(ov08x, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov08x40_ctrl_ops = { + .s_ctrl = ov08x40_set_ctrl, +}; + +static int ov08x40_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order(GRBG) is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov08x40_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void ov08x40_update_pad_format(const struct ov08x40_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int ov08x40_do_get_pad_format(struct ov08x40 *ov08x, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &ov08x->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + fmt->format = *framefmt; + } else { + ov08x40_update_pad_format(ov08x->cur_mode, fmt); + } + + return 0; +} + +static int ov08x40_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + int ret; + + mutex_lock(&ov08x->mutex); + ret = ov08x40_do_get_pad_format(ov08x, sd_state, fmt); + mutex_unlock(&ov08x->mutex); + + return ret; +} + +static int +ov08x40_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + const struct ov08x40_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + s64 pixel_rate; + s64 link_freq; + + mutex_lock(&ov08x->mutex); + + /* Only one raw bayer(GRBG) order is supported */ + if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10) + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + ov08x40_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + *framefmt = fmt->format; + } else { + ov08x->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov08x->link_freq, mode->link_freq_index); + link_freq = link_freq_menu_items[mode->link_freq_index]; + pixel_rate = link_freq_to_pixel_rate(link_freq); + __v4l2_ctrl_s_ctrl_int64(ov08x->pixel_rate, pixel_rate); + + /* Update limits and set FPS to default */ + vblank_def = ov08x->cur_mode->vts_def - + ov08x->cur_mode->height; + vblank_min = ov08x->cur_mode->vts_min - + ov08x->cur_mode->height; + __v4l2_ctrl_modify_range(ov08x->vblank, vblank_min, + OV08X40_VTS_MAX + - ov08x->cur_mode->height, + 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov08x->vblank, vblank_def); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - ov08x->cur_mode->width; + __v4l2_ctrl_modify_range(ov08x->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&ov08x->mutex); + + return 0; +} + +static int ov08x40_start_streaming(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + const struct ov08x40_reg_list *reg_list; + int ret, link_freq_index; + + /* Get out of from software reset */ + ret = ov08x40_write_reg(ov08x, OV08X40_REG_SOFTWARE_RST, + OV08X40_REG_VALUE_08BIT, OV08X40_SOFTWARE_RST); + if (ret) { + dev_err(&client->dev, "%s failed to set powerup registers\n", + __func__); + return ret; + } + + link_freq_index = ov08x->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + + ret = ov08x40_write_reg_list(ov08x, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &ov08x->cur_mode->reg_list; + ret = ov08x40_write_reg_list(ov08x, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(ov08x->sd.ctrl_handler); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT, + OV08X40_REG_VALUE_08BIT, + OV08X40_MODE_STREAMING); +} + +/* Stop streaming */ +static int ov08x40_stop_streaming(struct ov08x40 *ov08x) +{ + return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT, + OV08X40_REG_VALUE_08BIT, OV08X40_MODE_STANDBY); +} + +static int ov08x40_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov08x->mutex); + if (ov08x->streaming == enable) { + mutex_unlock(&ov08x->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto err_unlock; + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = ov08x40_start_streaming(ov08x); + if (ret) + goto err_rpm_put; + } else { + ov08x40_stop_streaming(ov08x); + pm_runtime_put(&client->dev); + } + + ov08x->streaming = enable; + mutex_unlock(&ov08x->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&ov08x->mutex); + + return ret; +} + +static int __maybe_unused ov08x40_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov08x40 *ov08x = to_ov08x40(sd); + + if (ov08x->streaming) + ov08x40_stop_streaming(ov08x); + + return 0; +} + +static int __maybe_unused ov08x40_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov08x40 *ov08x = to_ov08x40(sd); + int ret; + + if (ov08x->streaming) { + ret = ov08x40_start_streaming(ov08x); + if (ret) + goto error; + } + + return 0; + +error: + ov08x40_stop_streaming(ov08x); + ov08x->streaming = false; + return ret; +} + +/* Verify chip ID */ +static int ov08x40_identify_module(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_CHIP_ID, + OV08X40_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV08X40_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV08X40_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops ov08x40_video_ops = { + .s_stream = ov08x40_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov08x40_pad_ops = { + .enum_mbus_code = ov08x40_enum_mbus_code, + .get_fmt = ov08x40_get_pad_format, + .set_fmt = ov08x40_set_pad_format, + .enum_frame_size = ov08x40_enum_frame_size, +}; + +static const struct v4l2_subdev_ops ov08x40_subdev_ops = { + .video = &ov08x40_video_ops, + .pad = &ov08x40_pad_ops, +}; + +static const struct media_entity_operations ov08x40_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov08x40_internal_ops = { + .open = ov08x40_open, +}; + +static int ov08x40_init_controls(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 hblank; + s64 pixel_rate_min; + s64 pixel_rate_max; + const struct ov08x40_mode *mode; + u32 max; + int ret; + + ctrl_hdlr = &ov08x->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + mutex_init(&ov08x->mutex); + ctrl_hdlr->lock = &ov08x->mutex; + max = ARRAY_SIZE(link_freq_menu_items) - 1; + ov08x->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov08x40_ctrl_ops, + V4L2_CID_LINK_FREQ, + max, + 0, + link_freq_menu_items); + if (ov08x->link_freq) + ov08x->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]); + pixel_rate_min = 0; + /* By default, PIXEL_RATE is read only */ + ov08x->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, pixel_rate_max); + + mode = ov08x->cur_mode; + vblank_def = mode->vts_def - mode->height; + vblank_min = mode->vts_min - mode->height; + ov08x->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_VBLANK, + vblank_min, + OV08X40_VTS_MAX - mode->height, 1, + vblank_def); + + hblank = link_freq_configs[mode->link_freq_index].pixels_per_line - + mode->width; + ov08x->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_HBLANK, + hblank, hblank, 1, hblank); + if (ov08x->hblank) + ov08x->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + exposure_max = mode->vts_def - OV08X40_EXPOSURE_MAX_MARGIN; + ov08x->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_EXPOSURE, + OV08X40_EXPOSURE_MIN, + exposure_max, OV08X40_EXPOSURE_STEP, + exposure_max); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV08X40_ANA_GAIN_MIN, OV08X40_ANA_GAIN_MAX, + OV08X40_ANA_GAIN_STEP, OV08X40_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV08X40_DGTL_GAIN_MIN, OV08X40_DGTL_GAIN_MAX, + OV08X40_DGTL_GAIN_STEP, OV08X40_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov08x40_test_pattern_menu) - 1, + 0, 0, ov08x40_test_pattern_menu); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov08x40_ctrl_ops, + &props); + if (ret) + goto error; + + ov08x->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&ov08x->mutex); + + return ret; +} + +static void ov08x40_free_controls(struct ov08x40 *ov08x) +{ + v4l2_ctrl_handler_free(ov08x->sd.ctrl_handler); + mutex_destroy(&ov08x->mutex); +} + +static int ov08x40_check_hwcfg(struct device *dev) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned int i, j; + int ret; + u32 ext_clk; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &ext_clk); + if (ret) { + dev_err(dev, "can't get clock frequency"); + return ret; + } + + if (ext_clk != OV08X40_EXT_CLK) { + dev_err(dev, "external clock %d is not supported", + ext_clk); + 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 != OV08X40_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto out_err; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto out_err; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto out_err; + } + } + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov08x40_probe(struct i2c_client *client) +{ + struct ov08x40 *ov08x; + int ret; + + /* Check HW config */ + ret = ov08x40_check_hwcfg(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to check hwcfg: %d", ret); + return ret; + } + + ov08x = devm_kzalloc(&client->dev, sizeof(*ov08x), GFP_KERNEL); + if (!ov08x) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov08x->sd, client, &ov08x40_subdev_ops); + + /* Check module identity */ + ret = ov08x40_identify_module(ov08x); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + return ret; + } + + /* Set default mode to max resolution */ + ov08x->cur_mode = &supported_modes[0]; + + ret = ov08x40_init_controls(ov08x); + if (ret) + return ret; + + /* Initialize subdev */ + ov08x->sd.internal_ops = &ov08x40_internal_ops; + ov08x->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov08x->sd.entity.ops = &ov08x40_subdev_entity_ops; + ov08x->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + ov08x->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov08x->sd.entity, 1, &ov08x->pad); + if (ret) { + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&ov08x->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&ov08x->sd.entity); + +error_handler_free: + ov08x40_free_controls(ov08x); + + return ret; +} + +static void ov08x40_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov08x40 *ov08x = to_ov08x40(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + ov08x40_free_controls(ov08x); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct dev_pm_ops ov08x40_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov08x40_suspend, ov08x40_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov08x40_acpi_ids[] = { + {"OVTI08F4"}, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov08x40_acpi_ids); +#endif + +static struct i2c_driver ov08x40_i2c_driver = { + .driver = { + .name = "ov08x40", + .pm = &ov08x40_pm_ops, + .acpi_match_table = ACPI_PTR(ov08x40_acpi_ids), + }, + .probe_new = ov08x40_probe, + .remove = ov08x40_remove, +}; + +module_i2c_driver(ov08x40_i2c_driver); + +MODULE_AUTHOR("Jason Chen <jason.z.chen@intel.com>"); +MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>"); +MODULE_DESCRIPTION("OmniVision OV08X40 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c index e618b613e078..69a7a2c590db 100644 --- a/drivers/media/i2c/ov13858.c +++ b/drivers/media/i2c/ov13858.c @@ -1698,8 +1698,7 @@ static void ov13858_free_controls(struct ov13858 *ov13858) mutex_destroy(&ov13858->mutex); } -static int ov13858_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int ov13858_probe(struct i2c_client *client) { struct ov13858 *ov13858; int ret; @@ -1807,7 +1806,7 @@ static struct i2c_driver ov13858_i2c_driver = { .pm = &ov13858_pm_ops, .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), }, - .probe = ov13858_probe, + .probe_new = ov13858_probe, .remove = ov13858_remove, .id_table = ov13858_id_table, }; diff --git a/drivers/media/i2c/ov2640.c b/drivers/media/i2c/ov2640.c index 29ed0ef8c033..39d56838a4ef 100644 --- a/drivers/media/i2c/ov2640.c +++ b/drivers/media/i2c/ov2640.c @@ -16,9 +16,7 @@ #include <linux/clk.h> #include <linux/slab.h> #include <linux/delay.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> -#include <linux/of_gpio.h> #include <linux/v4l2-mediabus.h> #include <linux/videodev2.h> diff --git a/drivers/media/i2c/ov2680.c b/drivers/media/i2c/ov2680.c index de66d3395a4d..54153bf66bdd 100644 --- a/drivers/media/i2c/ov2680.c +++ b/drivers/media/i2c/ov2680.c @@ -967,6 +967,8 @@ static int ov2680_v4l2_register(struct ov2680_dev *sensor) ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrls->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + ctrls->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c index 5d74ad479214..f3731f932a94 100644 --- a/drivers/media/i2c/ov2740.c +++ b/drivers/media/i2c/ov2740.c @@ -77,7 +77,6 @@ #define OV2740_REG_OTP_CUSTOMER 0x7010 struct nvm_data { - struct i2c_client *client; struct nvmem_device *nvmem; struct regmap *regmap; char *nvm_buffer; @@ -379,7 +378,7 @@ static int ov2740_read_reg(struct ov2740 *ov2740, u16 reg, u16 len, u32 *val) struct i2c_msg msgs[2]; u8 addr_buf[2]; u8 data_buf[4] = {0}; - int ret = 0; + int ret; if (len > sizeof(data_buf)) return -EINVAL; @@ -407,7 +406,7 @@ static int ov2740_write_reg(struct ov2740 *ov2740, u16 reg, u16 len, u32 val) { struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); u8 buf[6]; - int ret = 0; + int ret; if (len > 4) return -EINVAL; @@ -427,14 +426,14 @@ static int ov2740_write_reg_list(struct ov2740 *ov2740, { struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); unsigned int i; - int ret = 0; + int ret; for (i = 0; i < r_list->num_of_regs; i++) { ret = ov2740_write_reg(ov2740, r_list->regs[i].address, 1, r_list->regs[i].val); if (ret) { dev_err_ratelimited(&client->dev, - "write reg 0x%4.4x return err = %d", + "write reg 0x%4.4x return err = %d\n", r_list->regs[i].address, ret); return ret; } @@ -457,7 +456,7 @@ static int ov2740_identify_module(struct ov2740 *ov2740) return ret; if (val != OV2740_CHIP_ID) { - dev_err(&client->dev, "chip id mismatch: %x!=%x", + dev_err(&client->dev, "chip id mismatch: %x != %x\n", OV2740_CHIP_ID, val); return -ENXIO; } @@ -469,7 +468,7 @@ static int ov2740_identify_module(struct ov2740 *ov2740) static int ov2740_update_digital_gain(struct ov2740 *ov2740, u32 d_gain) { - int ret = 0; + int ret; ret = ov2740_write_reg(ov2740, OV2740_REG_GROUP_ACCESS, 1, OV2740_GROUP_HOLD_START); @@ -513,7 +512,7 @@ static int ov2740_set_ctrl(struct v4l2_ctrl *ctrl) struct ov2740, ctrl_handler); struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); s64 exposure_max; - int ret = 0; + int ret; /* Propagate change of current control to all related controls */ if (ctrl->id == V4L2_CID_VBLANK) { @@ -576,7 +575,7 @@ static int ov2740_init_controls(struct ov2740 *ov2740) s64 exposure_max, h_blank, pixel_rate; u32 vblank_min, vblank_max, vblank_default; int size; - int ret = 0; + int ret; ctrl_hdlr = &ov2740->ctrl_handler; ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); @@ -649,34 +648,28 @@ static void ov2740_update_pad_format(const struct ov2740_mode *mode, static int ov2740_load_otp_data(struct nvm_data *nvm) { - struct i2c_client *client; - struct ov2740 *ov2740; + struct device *dev = regmap_get_device(nvm->regmap); + struct ov2740 *ov2740 = to_ov2740(dev_get_drvdata(dev)); u32 isp_ctrl00 = 0; u32 isp_ctrl01 = 0; int ret; - if (!nvm) - return -EINVAL; - if (nvm->nvm_buffer) return 0; - client = nvm->client; - ov2740 = to_ov2740(i2c_get_clientdata(client)); - nvm->nvm_buffer = kzalloc(CUSTOMER_USE_OTP_SIZE, GFP_KERNEL); if (!nvm->nvm_buffer) return -ENOMEM; ret = ov2740_read_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, &isp_ctrl00); if (ret) { - dev_err(&client->dev, "failed to read ISP CTRL00\n"); + dev_err(dev, "failed to read ISP CTRL00\n"); goto err; } ret = ov2740_read_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, &isp_ctrl01); if (ret) { - dev_err(&client->dev, "failed to read ISP CTRL01\n"); + dev_err(dev, "failed to read ISP CTRL01\n"); goto err; } @@ -684,7 +677,7 @@ static int ov2740_load_otp_data(struct nvm_data *nvm) ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, isp_ctrl00 & ~BIT(5)); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL00\n"); + dev_err(dev, "failed to set ISP CTRL00\n"); goto err; } @@ -692,14 +685,14 @@ static int ov2740_load_otp_data(struct nvm_data *nvm) ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, isp_ctrl01 & ~BIT(7)); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL01\n"); + dev_err(dev, "failed to set ISP CTRL01\n"); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STREAMING); if (ret) { - dev_err(&client->dev, "failed to set streaming mode\n"); + dev_err(dev, "failed to set streaming mode\n"); goto err; } @@ -712,26 +705,26 @@ static int ov2740_load_otp_data(struct nvm_data *nvm) ret = regmap_bulk_read(nvm->regmap, OV2740_REG_OTP_CUSTOMER, nvm->nvm_buffer, CUSTOMER_USE_OTP_SIZE); if (ret) { - dev_err(&client->dev, "failed to read OTP data, ret %d\n", ret); + dev_err(dev, "failed to read OTP data, ret %d\n", ret); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STANDBY); if (ret) { - dev_err(&client->dev, "failed to set streaming mode\n"); + dev_err(dev, "failed to set streaming mode\n"); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, isp_ctrl01); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL01\n"); + dev_err(dev, "failed to set ISP CTRL01\n"); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, isp_ctrl00); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL00\n"); + dev_err(dev, "failed to set ISP CTRL00\n"); goto err; } @@ -746,29 +739,29 @@ err: static int ov2740_start_streaming(struct ov2740 *ov2740) { struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); - struct nvm_data *nvm = ov2740->nvm; const struct ov2740_reg_list *reg_list; int link_freq_index; - int ret = 0; + int ret; ret = ov2740_identify_module(ov2740); if (ret) return ret; - ov2740_load_otp_data(nvm); + if (ov2740->nvm) + ov2740_load_otp_data(ov2740->nvm); link_freq_index = ov2740->cur_mode->link_freq_index; reg_list = &link_freq_configs[link_freq_index].reg_list; ret = ov2740_write_reg_list(ov2740, reg_list); if (ret) { - dev_err(&client->dev, "failed to set plls"); + dev_err(&client->dev, "failed to set plls\n"); return ret; } reg_list = &ov2740->cur_mode->reg_list; ret = ov2740_write_reg_list(ov2740, reg_list); if (ret) { - dev_err(&client->dev, "failed to set mode"); + dev_err(&client->dev, "failed to set mode\n"); return ret; } @@ -779,7 +772,7 @@ static int ov2740_start_streaming(struct ov2740 *ov2740) ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STREAMING); if (ret) - dev_err(&client->dev, "failed to start streaming"); + dev_err(&client->dev, "failed to start streaming\n"); return ret; } @@ -790,7 +783,7 @@ static void ov2740_stop_streaming(struct ov2740 *ov2740) if (ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STANDBY)) - dev_err(&client->dev, "failed to stop streaming"); + dev_err(&client->dev, "failed to stop streaming\n"); } static int ov2740_set_stream(struct v4l2_subdev *sd, int enable) @@ -827,7 +820,7 @@ static int ov2740_set_stream(struct v4l2_subdev *sd, int enable) return ret; } -static int __maybe_unused ov2740_suspend(struct device *dev) +static int ov2740_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct ov2740 *ov2740 = to_ov2740(sd); @@ -841,7 +834,7 @@ static int __maybe_unused ov2740_suspend(struct device *dev) return 0; } -static int __maybe_unused ov2740_resume(struct device *dev) +static int ov2740_resume(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct ov2740 *ov2740 = to_ov2740(sd); @@ -998,17 +991,14 @@ static int ov2740_check_hwcfg(struct device *dev) int ret; unsigned int i, j; - if (!fwnode) - return -ENXIO; - ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); if (ret) return ret; - if (mclk != OV2740_MCLK) { - dev_err(dev, "external clock %d is not supported", mclk); - return -EINVAL; - } + if (mclk != OV2740_MCLK) + return dev_err_probe(dev, -EINVAL, + "external clock %d is not supported\n", + mclk); ep = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!ep) @@ -1020,15 +1010,14 @@ static int ov2740_check_hwcfg(struct device *dev) return ret; if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV2740_DATA_LANES) { - dev_err(dev, "number of CSI2 data lanes %d is not supported", - bus_cfg.bus.mipi_csi2.num_data_lanes); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, + "number of CSI2 data lanes %d is not supported\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); goto check_hwcfg_error; } if (!bus_cfg.nr_of_link_frequencies) { - dev_err(dev, "no link frequencies defined"); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, "no link frequencies defined\n"); goto check_hwcfg_error; } @@ -1040,9 +1029,9 @@ static int ov2740_check_hwcfg(struct device *dev) } if (j == bus_cfg.nr_of_link_frequencies) { - dev_err(dev, "no link frequency %lld supported", - link_freq_menu_items[i]); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, + "no link frequency %lld supported\n", + link_freq_menu_items[i]); goto check_hwcfg_error; } } @@ -1069,9 +1058,8 @@ static int ov2740_nvmem_read(void *priv, unsigned int off, void *val, size_t count) { struct nvm_data *nvm = priv; - struct v4l2_subdev *sd = i2c_get_clientdata(nvm->client); - struct device *dev = &nvm->client->dev; - struct ov2740 *ov2740 = to_ov2740(sd); + struct device *dev = regmap_get_device(nvm->regmap); + struct ov2740 *ov2740 = to_ov2740(dev_get_drvdata(dev)); int ret = 0; mutex_lock(&ov2740->mutex); @@ -1104,7 +1092,6 @@ static int ov2740_register_nvmem(struct i2c_client *client, struct nvmem_config nvmem_config = { }; struct regmap *regmap; struct device *dev = &client->dev; - int ret; nvm = devm_kzalloc(dev, sizeof(*nvm), GFP_KERNEL); if (!nvm) @@ -1118,7 +1105,6 @@ static int ov2740_register_nvmem(struct i2c_client *client, return PTR_ERR(regmap); nvm->regmap = regmap; - nvm->client = client; nvmem_config.name = dev_name(dev); nvmem_config.dev = dev; @@ -1135,26 +1121,23 @@ static int ov2740_register_nvmem(struct i2c_client *client, nvmem_config.size = CUSTOMER_USE_OTP_SIZE; nvm->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvm->nvmem)) + return PTR_ERR(nvm->nvmem); - ret = PTR_ERR_OR_ZERO(nvm->nvmem); - if (!ret) - ov2740->nvm = nvm; - - return ret; + ov2740->nvm = nvm; + return 0; } static int ov2740_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct ov2740 *ov2740; - int ret = 0; bool full_power; + int ret; ret = ov2740_check_hwcfg(&client->dev); - if (ret) { - dev_err(&client->dev, "failed to check HW configuration: %d", - ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to check HW configuration\n"); ov2740 = devm_kzalloc(&client->dev, sizeof(*ov2740), GFP_KERNEL); if (!ov2740) @@ -1164,17 +1147,15 @@ static int ov2740_probe(struct i2c_client *client) full_power = acpi_dev_state_d0(&client->dev); if (full_power) { ret = ov2740_identify_module(ov2740); - if (ret) { - dev_err(&client->dev, "failed to find sensor: %d", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to find sensor\n"); } mutex_init(&ov2740->mutex); ov2740->cur_mode = &supported_modes[0]; ret = ov2740_init_controls(ov2740); if (ret) { - dev_err(&client->dev, "failed to init controls: %d", ret); + dev_err_probe(dev, ret, "failed to init controls\n"); goto probe_error_v4l2_ctrl_handler_free; } @@ -1185,14 +1166,13 @@ static int ov2740_probe(struct i2c_client *client) ov2740->pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&ov2740->sd.entity, 1, &ov2740->pad); if (ret) { - dev_err(&client->dev, "failed to init entity pads: %d", ret); + dev_err_probe(dev, ret, "failed to init entity pads\n"); goto probe_error_v4l2_ctrl_handler_free; } ret = v4l2_async_register_subdev_sensor(&ov2740->sd); if (ret < 0) { - dev_err(&client->dev, "failed to register V4L2 subdev: %d", - ret); + dev_err_probe(dev, ret, "failed to register V4L2 subdev\n"); goto probe_error_media_entity_cleanup; } @@ -1218,9 +1198,7 @@ probe_error_v4l2_ctrl_handler_free: return ret; } -static const struct dev_pm_ops ov2740_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(ov2740_suspend, ov2740_resume) -}; +static DEFINE_SIMPLE_DEV_PM_OPS(ov2740_pm_ops, ov2740_suspend, ov2740_resume); static const struct acpi_device_id ov2740_acpi_ids[] = { {"INT3474"}, @@ -1232,7 +1210,7 @@ MODULE_DEVICE_TABLE(acpi, ov2740_acpi_ids); static struct i2c_driver ov2740_i2c_driver = { .driver = { .name = "ov2740", - .pm = &ov2740_pm_ops, + .pm = pm_sleep_ptr(&ov2740_pm_ops), .acpi_match_table = ov2740_acpi_ids, }, .probe_new = ov2740_probe, diff --git a/drivers/media/i2c/ov4689.c b/drivers/media/i2c/ov4689.c new file mode 100644 index 000000000000..c602e507d42b --- /dev/null +++ b/drivers/media/i2c/ov4689.c @@ -0,0 +1,1018 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ov4689 driver + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + * Copyright (C) 2022 Mikhail Rudenko + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-fwnode.h> + +#define CHIP_ID 0x004688 +#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_STREAMING BIT(0) + +#define OV4689_REG_EXPOSURE 0x3500 +#define OV4689_EXPOSURE_MIN 4 +#define OV4689_EXPOSURE_STEP 1 +#define OV4689_VTS_MAX 0x7fff + +#define OV4689_REG_GAIN_H 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_DEFAULT 0x80 + +#define OV4689_REG_TEST_PATTERN 0x5040 +#define OV4689_TEST_PATTERN_ENABLE 0x80 +#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 + +static const char *const ov4689_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +struct regval { + u16 addr; + u8 val; +}; + +enum ov4689_mode_id { + OV4689_MODE_2688_1520 = 0, + OV4689_NUM_MODES, +}; + +struct ov4689_mode { + enum ov4689_mode_id id; + u32 width; + u32 height; + u32 max_fps; + u32 hts_def; + u32 vts_def; + u32 exp_def; + u32 pixel_rate; + u32 sensor_width; + u32 sensor_height; + u32 crop_top; + u32 crop_left; + const struct regval *reg_list; +}; + +struct ov4689 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(ov4689_supply_names)]; + + struct v4l2_subdev subdev; + struct media_pad pad; + + u32 clock_rate; + + struct mutex mutex; /* lock to protect streaming, ctrls and cur_mode */ + bool streaming; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + + const struct ov4689_mode *cur_mode; +}; + +#define to_ov4689(sd) container_of(sd, struct ov4689, subdev) + +struct ov4689_gain_range { + u32 logical_min; + u32 logical_max; + u32 offset; + u32 divider; + u32 physical_min; + u32 physical_max; +}; + +/* + * Xclk 24Mhz + * max_framerate 30fps + * mipi_datarate per lane 1008Mbps + */ +static const struct regval ov4689_2688x1520_regs[] = { + {0x0103, 0x01}, {0x3638, 0x00}, {0x0300, 0x00}, + {0x0302, 0x2a}, {0x0303, 0x00}, {0x0304, 0x03}, + {0x030b, 0x00}, {0x030d, 0x1e}, {0x030e, 0x04}, + {0x030f, 0x01}, {0x0312, 0x01}, {0x031e, 0x00}, + {0x3000, 0x20}, {0x3002, 0x00}, {0x3018, 0x72}, + {0x3020, 0x93}, {0x3021, 0x03}, {0x3022, 0x01}, + {0x3031, 0x0a}, {0x303f, 0x0c}, {0x3305, 0xf1}, + {0x3307, 0x04}, {0x3309, 0x29}, {0x3500, 0x00}, + {0x3501, 0x60}, {0x3502, 0x00}, {0x3503, 0x04}, + {0x3504, 0x00}, {0x3505, 0x00}, {0x3506, 0x00}, + {0x3507, 0x00}, {0x3508, 0x00}, {0x3509, 0x80}, + {0x350a, 0x00}, {0x350b, 0x00}, {0x350c, 0x00}, + {0x350d, 0x00}, {0x350e, 0x00}, {0x350f, 0x80}, + {0x3510, 0x00}, {0x3511, 0x00}, {0x3512, 0x00}, + {0x3513, 0x00}, {0x3514, 0x00}, {0x3515, 0x80}, + {0x3516, 0x00}, {0x3517, 0x00}, {0x3518, 0x00}, + {0x3519, 0x00}, {0x351a, 0x00}, {0x351b, 0x80}, + {0x351c, 0x00}, {0x351d, 0x00}, {0x351e, 0x00}, + {0x351f, 0x00}, {0x3520, 0x00}, {0x3521, 0x80}, + {0x3522, 0x08}, {0x3524, 0x08}, {0x3526, 0x08}, + {0x3528, 0x08}, {0x352a, 0x08}, {0x3602, 0x00}, + {0x3603, 0x40}, {0x3604, 0x02}, {0x3605, 0x00}, + {0x3606, 0x00}, {0x3607, 0x00}, {0x3609, 0x12}, + {0x360a, 0x40}, {0x360c, 0x08}, {0x360f, 0xe5}, + {0x3608, 0x8f}, {0x3611, 0x00}, {0x3613, 0xf7}, + {0x3616, 0x58}, {0x3619, 0x99}, {0x361b, 0x60}, + {0x361c, 0x7a}, {0x361e, 0x79}, {0x361f, 0x02}, + {0x3632, 0x00}, {0x3633, 0x10}, {0x3634, 0x10}, + {0x3635, 0x10}, {0x3636, 0x15}, {0x3646, 0x86}, + {0x364a, 0x0b}, {0x3700, 0x17}, {0x3701, 0x22}, + {0x3703, 0x10}, {0x370a, 0x37}, {0x3705, 0x00}, + {0x3706, 0x63}, {0x3709, 0x3c}, {0x370b, 0x01}, + {0x370c, 0x30}, {0x3710, 0x24}, {0x3711, 0x0c}, + {0x3716, 0x00}, {0x3720, 0x28}, {0x3729, 0x7b}, + {0x372a, 0x84}, {0x372b, 0xbd}, {0x372c, 0xbc}, + {0x372e, 0x52}, {0x373c, 0x0e}, {0x373e, 0x33}, + {0x3743, 0x10}, {0x3744, 0x88}, {0x3745, 0xc0}, + {0x374a, 0x43}, {0x374c, 0x00}, {0x374e, 0x23}, + {0x3751, 0x7b}, {0x3752, 0x84}, {0x3753, 0xbd}, + {0x3754, 0xbc}, {0x3756, 0x52}, {0x375c, 0x00}, + {0x3760, 0x00}, {0x3761, 0x00}, {0x3762, 0x00}, + {0x3763, 0x00}, {0x3764, 0x00}, {0x3767, 0x04}, + {0x3768, 0x04}, {0x3769, 0x08}, {0x376a, 0x08}, + {0x376b, 0x20}, {0x376c, 0x00}, {0x376d, 0x00}, + {0x376e, 0x00}, {0x3773, 0x00}, {0x3774, 0x51}, + {0x3776, 0xbd}, {0x3777, 0xbd}, {0x3781, 0x18}, + {0x3783, 0x25}, {0x3798, 0x1b}, {0x3800, 0x00}, + {0x3801, 0x08}, {0x3802, 0x00}, {0x3803, 0x04}, + {0x3804, 0x0a}, {0x3805, 0x97}, {0x3806, 0x05}, + {0x3807, 0xfb}, {0x3808, 0x0a}, {0x3809, 0x80}, + {0x380a, 0x05}, {0x380b, 0xf0}, {0x380c, 0x0a}, + {0x380d, 0x0e}, {0x380e, 0x06}, {0x380f, 0x12}, + {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, + {0x3813, 0x04}, {0x3814, 0x01}, {0x3815, 0x01}, + {0x3819, 0x01}, {0x3820, 0x00}, {0x3821, 0x06}, + {0x3829, 0x00}, {0x382a, 0x01}, {0x382b, 0x01}, + {0x382d, 0x7f}, {0x3830, 0x04}, {0x3836, 0x01}, + {0x3837, 0x00}, {0x3841, 0x02}, {0x3846, 0x08}, + {0x3847, 0x07}, {0x3d85, 0x36}, {0x3d8c, 0x71}, + {0x3d8d, 0xcb}, {0x3f0a, 0x00}, {0x4000, 0xf1}, + {0x4001, 0x40}, {0x4002, 0x04}, {0x4003, 0x14}, + {0x400e, 0x00}, {0x4011, 0x00}, {0x401a, 0x00}, + {0x401b, 0x00}, {0x401c, 0x00}, {0x401d, 0x00}, + {0x401f, 0x00}, {0x4020, 0x00}, {0x4021, 0x10}, + {0x4022, 0x07}, {0x4023, 0xcf}, {0x4024, 0x09}, + {0x4025, 0x60}, {0x4026, 0x09}, {0x4027, 0x6f}, + {0x4028, 0x00}, {0x4029, 0x02}, {0x402a, 0x06}, + {0x402b, 0x04}, {0x402c, 0x02}, {0x402d, 0x02}, + {0x402e, 0x0e}, {0x402f, 0x04}, {0x4302, 0xff}, + {0x4303, 0xff}, {0x4304, 0x00}, {0x4305, 0x00}, + {0x4306, 0x00}, {0x4308, 0x02}, {0x4500, 0x6c}, + {0x4501, 0xc4}, {0x4502, 0x40}, {0x4503, 0x01}, + {0x4601, 0xa7}, {0x4800, 0x04}, {0x4813, 0x08}, + {0x481f, 0x40}, {0x4829, 0x78}, {0x4837, 0x10}, + {0x4b00, 0x2a}, {0x4b0d, 0x00}, {0x4d00, 0x04}, + {0x4d01, 0x42}, {0x4d02, 0xd1}, {0x4d03, 0x93}, + {0x4d04, 0xf5}, {0x4d05, 0xc1}, {0x5000, 0xf3}, + {0x5001, 0x11}, {0x5004, 0x00}, {0x500a, 0x00}, + {0x500b, 0x00}, {0x5032, 0x00}, {0x5040, 0x00}, + {0x5050, 0x0c}, {0x5500, 0x00}, {0x5501, 0x10}, + {0x5502, 0x01}, {0x5503, 0x0f}, {0x8000, 0x00}, + {0x8001, 0x00}, {0x8002, 0x00}, {0x8003, 0x00}, + {0x8004, 0x00}, {0x8005, 0x00}, {0x8006, 0x00}, + {0x8007, 0x00}, {0x8008, 0x00}, {0x3638, 0x00}, + {REG_NULL, 0x00}, +}; + +static const struct ov4689_mode supported_modes[] = { + { + .id = OV4689_MODE_2688_1520, + .width = 2688, + .height = 1520, + .sensor_width = 2720, + .sensor_height = 1536, + .crop_top = 8, + .crop_left = 16, + .max_fps = 30, + .exp_def = 1536, + .hts_def = 4 * 2574, + .vts_def = 1554, + .pixel_rate = 480000000, + .reg_list = ov4689_2688x1520_regs, + }, +}; + +static const u64 link_freq_menu_items[] = { 504000000 }; + +static const char *const ov4689_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* + * These coefficients are based on those used in Rockchip's camera + * engine, with minor tweaks for continuity. + */ +static const struct ov4689_gain_range ov4689_gain_ranges[] = { + { + .logical_min = 0, + .logical_max = 255, + .offset = 0, + .divider = 1, + .physical_min = 0, + .physical_max = 255, + }, + { + .logical_min = 256, + .logical_max = 511, + .offset = 252, + .divider = 2, + .physical_min = 376, + .physical_max = 504, + }, + { + .logical_min = 512, + .logical_max = 1023, + .offset = 758, + .divider = 4, + .physical_min = 884, + .physical_max = 1012, + }, + { + .logical_min = 1024, + .logical_max = 2047, + .offset = 1788, + .divider = 8, + .physical_min = 1912, + .physical_max = 2047, + }, +}; + +/* 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 *)®_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, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov4689_set_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_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, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + + return 0; +} + +static int ov4689_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->max_height = supported_modes[fse->index].height; + fse->min_height = supported_modes[fse->index].height; + + return 0; +} + +static int ov4689_enable_test_pattern(struct ov4689 *ov4689, u32 pattern) +{ + u32 val; + + if (pattern) + val = (pattern - 1) | OV4689_TEST_PATTERN_ENABLE; + else + val = OV4689_TEST_PATTERN_DISABLE; + + return ov4689_write_reg(ov4689->client, OV4689_REG_TEST_PATTERN, + OV4689_REG_VALUE_08BIT, val); +} + +static int ov4689_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + const struct ov4689_mode *mode = to_ov4689(sd)->cur_mode; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = mode->sensor_width; + sel->r.height = mode->sensor_height; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = mode->crop_top; + sel->r.left = mode->crop_left; + sel->r.width = mode->width; + sel->r.height = mode->height; + return 0; + } + + return -EINVAL; +} + +static int ov4689_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ov4689 *ov4689 = to_ov4689(sd); + struct i2c_client *client = ov4689->client; + int ret = 0; + + mutex_lock(&ov4689->mutex); + + on = !!on; + if (on == ov4689->streaming) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto unlock_and_return; + + ret = ov4689_write_array(ov4689->client, + ov4689->cur_mode->reg_list); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + ret = __v4l2_ctrl_handler_setup(&ov4689->ctrl_handler); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + ret = ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, + OV4689_REG_VALUE_08BIT, + OV4689_MODE_STREAMING); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, + OV4689_REG_VALUE_08BIT, + OV4689_MODE_SW_STANDBY); + pm_runtime_put(&client->dev); + } + + ov4689->streaming = on; + +unlock_and_return: + mutex_unlock(&ov4689->mutex); + + return ret; +} + +/* Calculate the delay in us by clock rate and clock cycles */ +static inline u32 ov4689_cal_delay(struct ov4689 *ov4689, u32 cycles) +{ + return DIV_ROUND_UP(cycles * 1000, + DIV_ROUND_UP(ov4689->clock_rate, 1000)); +} + +static int __maybe_unused ov4689_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov4689 *ov4689 = to_ov4689(sd); + u32 delay_us; + int ret; + + ret = clk_prepare_enable(ov4689->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + gpiod_set_value_cansleep(ov4689->reset_gpio, 1); + + ret = regulator_bulk_enable(ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + gpiod_set_value_cansleep(ov4689->reset_gpio, 0); + usleep_range(500, 1000); + gpiod_set_value_cansleep(ov4689->pwdn_gpio, 0); + + /* 8192 cycles prior to first SCCB transaction */ + delay_us = ov4689_cal_delay(ov4689, 8192); + usleep_range(delay_us, delay_us * 2); + + return 0; + +disable_clk: + clk_disable_unprepare(ov4689->xvclk); + + return ret; +} + +static int __maybe_unused ov4689_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov4689 *ov4689 = to_ov4689(sd); + + gpiod_set_value_cansleep(ov4689->pwdn_gpio, 1); + clk_disable_unprepare(ov4689->xvclk); + gpiod_set_value_cansleep(ov4689->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); + return 0; +} + +static int ov4689_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov4689 *ov4689 = to_ov4689(sd); + struct v4l2_mbus_framefmt *try_fmt; + + mutex_lock(&ov4689->mutex); + + try_fmt = v4l2_subdev_get_try_format(sd, fh->state, 0); + /* Initialize try_fmt */ + ov4689_fill_fmt(&supported_modes[OV4689_MODE_2688_1520], try_fmt); + + mutex_unlock(&ov4689->mutex); + + return 0; +} + +static const struct dev_pm_ops ov4689_pm_ops = { + 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 = { + .s_stream = ov4689_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov4689_pad_ops = { + .enum_mbus_code = ov4689_enum_mbus_code, + .enum_frame_size = ov4689_enum_frame_sizes, + .get_fmt = ov4689_get_fmt, + .set_fmt = ov4689_set_fmt, + .get_selection = ov4689_get_selection, +}; + +static const struct v4l2_subdev_ops ov4689_subdev_ops = { + .video = &ov4689_video_ops, + .pad = &ov4689_pad_ops, +}; + +/* + * Map userspace (logical) gain to sensor (physical) gain using + * ov4689_gain_ranges table. + */ +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; + unsigned int n; + + for (n = 0; n < ARRAY_SIZE(ov4689_gain_ranges); n++) { + if (logical_gain >= ov4689_gain_ranges[n].logical_min && + logical_gain <= ov4689_gain_ranges[n].logical_max) + break; + } + + if (n == ARRAY_SIZE(ov4689_gain_ranges)) { + dev_warn_ratelimited(dev, "no mapping found for gain %d\n", + logical_gain); + return -EINVAL; + } + + range = &ov4689_gain_ranges[n]; + + *result = clamp(range->offset + (logical_gain) / range->divider, + range->physical_min, range->physical_max); + return 0; +} + +static int ov4689_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov4689 *ov4689 = + container_of(ctrl->handler, struct ov4689, ctrl_handler); + struct i2c_client *client = ov4689->client; + int sensor_gain; + s64 max_expo; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max_expo = ov4689->cur_mode->height + ctrl->val - 4; + __v4l2_ctrl_modify_range(ov4689->exposure, + ov4689->exposure->minimum, max_expo, + ov4689->exposure->step, + ov4689->exposure->default_value); + break; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ov4689_write_reg(ov4689->client, OV4689_REG_EXPOSURE, + OV4689_REG_VALUE_24BIT, ctrl->val << 4); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ov4689_map_gain(ov4689, ctrl->val, &sensor_gain); + + 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; + case V4L2_CID_VBLANK: + ret = ov4689_write_reg(ov4689->client, OV4689_REG_VTS, + OV4689_REG_VALUE_16BIT, + ctrl->val + ov4689->cur_mode->height); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov4689_enable_test_pattern(ov4689, ctrl->val); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov4689_ctrl_ops = { + .s_ctrl = ov4689_set_ctrl, +}; + +static int ov4689_initialize_controls(struct ov4689 *ov4689) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov4689->subdev); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *handler; + const struct ov4689_mode *mode; + s64 exposure_max, vblank_def; + struct v4l2_ctrl *ctrl; + s64 h_blank_def; + int ret; + + handler = &ov4689->ctrl_handler; + mode = ov4689->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 10); + if (ret) + return ret; + handler->lock = &ov4689->mutex; + + ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, 0, 0, + link_freq_menu_items); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0, + mode->pixel_rate, 1, mode->pixel_rate); + + h_blank_def = mode->hts_def - mode->width; + ctrl = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, h_blank_def, + h_blank_def, 1, h_blank_def); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_VBLANK, + vblank_def, OV4689_VTS_MAX - mode->height, 1, + vblank_def); + + exposure_max = mode->vts_def - 4; + ov4689->exposure = + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_EXPOSURE, + OV4689_EXPOSURE_MIN, exposure_max, + OV4689_EXPOSURE_STEP, mode->exp_def); + + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + ov4689_gain_ranges[0].logical_min, + ov4689_gain_ranges[ARRAY_SIZE(ov4689_gain_ranges) - 1] + .logical_max, + OV4689_GAIN_STEP, OV4689_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(handler, &ov4689_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov4689_test_pattern_menu) - 1, + 0, 0, ov4689_test_pattern_menu); + + if (handler->error) { + ret = handler->error; + dev_err(&ov4689->client->dev, "Failed to init controls(%d)\n", + ret); + goto err_free_handler; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto err_free_handler; + + ret = v4l2_ctrl_new_fwnode_properties(handler, &ov4689_ctrl_ops, + &props); + if (ret) + goto err_free_handler; + + ov4689->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int ov4689_check_sensor_id(struct ov4689 *ov4689, + struct i2c_client *client) +{ + struct device *dev = &ov4689->client->dev; + u32 id = 0; + int ret; + + ret = ov4689_read_reg(client, OV4689_REG_CHIP_ID, + OV4689_REG_VALUE_16BIT, &id); + if (ret) { + dev_err(dev, "Cannot read sensor ID\n"); + return ret; + } + + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor ID %06x, expected %06x\n", + id, CHIP_ID); + return -ENODEV; + } + + dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID); + + return 0; +} + +static int ov4689_configure_regulators(struct ov4689 *ov4689) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ov4689_supply_names); i++) + ov4689->supplies[i].supply = ov4689_supply_names[i]; + + return devm_regulator_bulk_get(&ov4689->client->dev, + ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); +} + +static u64 ov4689_check_link_frequency(struct v4l2_fwnode_endpoint *ep) +{ + const u64 *freqs = link_freq_menu_items; + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < ep->nr_of_link_frequencies; j++) + if (freqs[i] == ep->link_frequencies[j]) + return freqs[i]; + } + + return 0; +} + +static int ov4689_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *endpoint; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!endpoint) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + fwnode_handle_put(endpoint); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV4689_LANES) { + dev_err(dev, "Only a 4-lane CSI2 config is supported"); + ret = -EINVAL; + goto out_free_bus_cfg; + } + + if (!ov4689_check_link_frequency(&bus_cfg)) { + dev_err(dev, "No supported link frequency found\n"); + ret = -EINVAL; + } + +out_free_bus_cfg: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov4689_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct v4l2_subdev *sd; + struct ov4689 *ov4689; + int ret; + + ret = ov4689_check_hwcfg(dev); + if (ret) + return ret; + + ov4689 = devm_kzalloc(dev, sizeof(*ov4689), GFP_KERNEL); + if (!ov4689) + return -ENOMEM; + + ov4689->client = client; + ov4689->cur_mode = &supported_modes[OV4689_MODE_2688_1520]; + + ov4689->xvclk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ov4689->xvclk)) + return dev_err_probe(dev, PTR_ERR(ov4689->xvclk), + "Failed to get external clock\n"); + + if (!ov4689->xvclk) { + dev_dbg(dev, + "No clock provided, using clock-frequency property\n"); + device_property_read_u32(dev, "clock-frequency", + &ov4689->clock_rate); + } else { + ov4689->clock_rate = clk_get_rate(ov4689->xvclk); + } + + if (ov4689->clock_rate != OV4689_XVCLK_FREQ) { + dev_err(dev, + "External clock rate mismatch: got %d Hz, expected %d Hz\n", + ov4689->clock_rate, OV4689_XVCLK_FREQ); + return -EINVAL; + } + + ov4689->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov4689->reset_gpio)) { + dev_err(dev, "Failed to get reset-gpios\n"); + return PTR_ERR(ov4689->reset_gpio); + } + + ov4689->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_LOW); + if (IS_ERR(ov4689->pwdn_gpio)) { + dev_err(dev, "Failed to get pwdn-gpios\n"); + return PTR_ERR(ov4689->pwdn_gpio); + } + + ret = ov4689_configure_regulators(ov4689); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get power regulators\n"); + + mutex_init(&ov4689->mutex); + + sd = &ov4689->subdev; + v4l2_i2c_subdev_init(sd, client, &ov4689_subdev_ops); + ret = ov4689_initialize_controls(ov4689); + if (ret) + goto err_destroy_mutex; + + ret = ov4689_power_on(dev); + if (ret) + goto err_free_handler; + + ret = ov4689_check_sensor_id(ov4689, client); + if (ret) + 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; + ret = media_entity_pads_init(&sd->entity, 1, &ov4689->pad); + if (ret < 0) + goto err_power_off; + + ret = v4l2_async_register_subdev_sensor(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: + media_entity_cleanup(&sd->entity); +err_power_off: + ov4689_power_off(dev); +err_free_handler: + v4l2_ctrl_handler_free(&ov4689->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&ov4689->mutex); + + return ret; +} + +static void ov4689_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov4689 *ov4689 = to_ov4689(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + v4l2_ctrl_handler_free(&ov4689->ctrl_handler); + mutex_destroy(&ov4689->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + ov4689_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id ov4689_of_match[] = { + { .compatible = "ovti,ov4689" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ov4689_of_match); + +static struct i2c_driver ov4689_i2c_driver = { + .driver = { + .name = "ov4689", + .pm = &ov4689_pm_ops, + .of_match_table = ov4689_of_match, + }, + .probe_new = ov4689_probe, + .remove = ov4689_remove, +}; + +module_i2c_driver(ov4689_i2c_driver); + +MODULE_DESCRIPTION("OmniVision ov4689 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c index 2d740397a5d4..e0f908af581b 100644 --- a/drivers/media/i2c/ov5640.c +++ b/drivers/media/i2c/ov5640.c @@ -2715,20 +2715,20 @@ static int ov5640_sensor_resume(struct device *dev) static int ov5640_try_frame_interval(struct ov5640_dev *sensor, struct v4l2_fract *fi, - u32 width, u32 height) + const struct ov5640_mode_info *mode_info) { - const struct ov5640_mode_info *mode; + const struct ov5640_mode_info *mode = mode_info; enum ov5640_frame_rate rate = OV5640_15_FPS; int minfps, maxfps, best_fps, fps; int i; minfps = ov5640_framerates[OV5640_15_FPS]; - maxfps = ov5640_framerates[OV5640_60_FPS]; + maxfps = ov5640_framerates[mode->max_fps]; if (fi->numerator == 0) { fi->denominator = maxfps; fi->numerator = 1; - rate = OV5640_60_FPS; + rate = mode->max_fps; goto find_mode; } @@ -2749,7 +2749,7 @@ static int ov5640_try_frame_interval(struct ov5640_dev *sensor, fi->denominator = best_fps; find_mode: - mode = ov5640_find_mode(sensor, width, height, false); + mode = ov5640_find_mode(sensor, mode->width, mode->height, false); return mode ? rate : -EINVAL; } @@ -3554,6 +3554,7 @@ static int ov5640_enum_frame_interval( struct v4l2_subdev_frame_interval_enum *fie) { struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *mode; struct v4l2_fract tpf; int ret; @@ -3562,11 +3563,14 @@ static int ov5640_enum_frame_interval( if (fie->index >= OV5640_NUM_FRAMERATES) return -EINVAL; + mode = ov5640_find_mode(sensor, fie->width, fie->height, false); + if (!mode) + return -EINVAL; + tpf.numerator = 1; tpf.denominator = ov5640_framerates[fie->index]; - ret = ov5640_try_frame_interval(sensor, &tpf, - fie->width, fie->height); + ret = ov5640_try_frame_interval(sensor, &tpf, mode); if (ret < 0) return -EINVAL; @@ -3605,9 +3609,7 @@ static int ov5640_s_frame_interval(struct v4l2_subdev *sd, mode = sensor->current_mode; - frame_rate = ov5640_try_frame_interval(sensor, &fi->interval, - mode->width, - mode->height); + frame_rate = ov5640_try_frame_interval(sensor, &fi->interval, mode); if (frame_rate < 0) { /* Always return a valid frame interval value */ fi->interval = sensor->frame_interval; @@ -3817,7 +3819,8 @@ static int ov5640_probe(struct i2c_client *client) sensor->current_mode = &ov5640_mode_data[OV5640_MODE_VGA_640_480]; sensor->last_mode = sensor->current_mode; - sensor->current_link_freq = OV5640_DEFAULT_LINK_FREQ; + sensor->current_link_freq = + ov5640_csi2_link_freqs[OV5640_DEFAULT_LINK_FREQ]; sensor->ae_target = 52; diff --git a/drivers/media/i2c/ov5645.c b/drivers/media/i2c/ov5645.c index 81e4e87e1821..c8999fc4f26f 100644 --- a/drivers/media/i2c/ov5645.c +++ b/drivers/media/i2c/ov5645.c @@ -14,9 +14,6 @@ * https://www.mail-archive.com/linux-media%40vger.kernel.org/msg92671.html */ -/* - */ - #include <linux/bitops.h> #include <linux/clk.h> #include <linux/delay.h> @@ -27,6 +24,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_graph.h> +#include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/types.h> @@ -108,7 +106,6 @@ struct ov5645 { u8 timing_tc_reg21; struct mutex power_lock; /* lock to protect power state */ - int power_count; struct gpio_desc *enable_gpio; struct gpio_desc *rst_gpio; @@ -635,8 +632,24 @@ static int ov5645_set_register_array(struct ov5645 *ov5645, return 0; } -static int ov5645_set_power_on(struct ov5645 *ov5645) +static int ov5645_set_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov5645 *ov5645 = to_ov5645(sd); + + ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x58); + gpiod_set_value_cansleep(ov5645->rst_gpio, 1); + gpiod_set_value_cansleep(ov5645->enable_gpio, 0); + clk_disable_unprepare(ov5645->xclk); + regulator_bulk_disable(OV5645_NUM_SUPPLIES, ov5645->supplies); + + return 0; +} + +static int ov5645_set_power_on(struct device *dev) { + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov5645 *ov5645 = to_ov5645(sd); int ret; ret = regulator_bulk_enable(OV5645_NUM_SUPPLIES, ov5645->supplies); @@ -658,57 +671,19 @@ static int ov5645_set_power_on(struct ov5645 *ov5645) msleep(20); - return 0; -} - -static void ov5645_set_power_off(struct ov5645 *ov5645) -{ - gpiod_set_value_cansleep(ov5645->rst_gpio, 1); - gpiod_set_value_cansleep(ov5645->enable_gpio, 0); - clk_disable_unprepare(ov5645->xclk); - regulator_bulk_disable(OV5645_NUM_SUPPLIES, ov5645->supplies); -} - -static int ov5645_s_power(struct v4l2_subdev *sd, int on) -{ - struct ov5645 *ov5645 = to_ov5645(sd); - int ret = 0; - - mutex_lock(&ov5645->power_lock); - - /* If the power count is modified from 0 to != 0 or from != 0 to 0, - * update the power state. - */ - if (ov5645->power_count == !on) { - if (on) { - ret = ov5645_set_power_on(ov5645); - if (ret < 0) - goto exit; - - ret = ov5645_set_register_array(ov5645, - ov5645_global_init_setting, + ret = ov5645_set_register_array(ov5645, ov5645_global_init_setting, ARRAY_SIZE(ov5645_global_init_setting)); - if (ret < 0) { - dev_err(ov5645->dev, - "could not set init registers\n"); - ov5645_set_power_off(ov5645); - goto exit; - } - - usleep_range(500, 1000); - } else { - ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x58); - ov5645_set_power_off(ov5645); - } + if (ret < 0) { + dev_err(ov5645->dev, "could not set init registers\n"); + goto exit; } - /* Update the power count. */ - ov5645->power_count += on ? 1 : -1; - WARN_ON(ov5645->power_count < 0); + usleep_range(500, 1000); -exit: - mutex_unlock(&ov5645->power_lock); + return 0; +exit: + ov5645_set_power_off(dev); return ret; } @@ -795,7 +770,7 @@ static int ov5645_s_ctrl(struct v4l2_ctrl *ctrl) int ret; mutex_lock(&ov5645->power_lock); - if (!ov5645->power_count) { + if (!pm_runtime_get_if_in_use(ov5645->dev)) { mutex_unlock(&ov5645->power_lock); return 0; } @@ -827,6 +802,8 @@ static int ov5645_s_ctrl(struct v4l2_ctrl *ctrl) break; } + pm_runtime_mark_last_busy(ov5645->dev); + pm_runtime_put_autosuspend(ov5645->dev); mutex_unlock(&ov5645->power_lock); return ret; @@ -991,6 +968,10 @@ static int ov5645_s_stream(struct v4l2_subdev *subdev, int enable) int ret; if (enable) { + ret = pm_runtime_resume_and_get(ov5645->dev); + if (ret < 0) + return ret; + ret = ov5645_set_register_array(ov5645, ov5645->current_mode->data, ov5645->current_mode->data_size); @@ -998,39 +979,44 @@ static int ov5645_s_stream(struct v4l2_subdev *subdev, int enable) dev_err(ov5645->dev, "could not set mode %dx%d\n", ov5645->current_mode->width, ov5645->current_mode->height); - return ret; + goto err_rpm_put; } ret = v4l2_ctrl_handler_setup(&ov5645->ctrls); if (ret < 0) { dev_err(ov5645->dev, "could not sync v4l2 controls\n"); - return ret; + goto err_rpm_put; } ret = ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x45); if (ret < 0) - return ret; + goto err_rpm_put; ret = ov5645_write_reg(ov5645, OV5645_SYSTEM_CTRL0, OV5645_SYSTEM_CTRL0_START); if (ret < 0) - return ret; + goto err_rpm_put; } else { ret = ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x40); if (ret < 0) - return ret; + goto stream_off_rpm_put; ret = ov5645_write_reg(ov5645, OV5645_SYSTEM_CTRL0, OV5645_SYSTEM_CTRL0_STOP); - if (ret < 0) - return ret; + + goto stream_off_rpm_put; } return 0; -} -static const struct v4l2_subdev_core_ops ov5645_core_ops = { - .s_power = ov5645_s_power, -}; +err_rpm_put: + pm_runtime_put_sync(ov5645->dev); + return ret; + +stream_off_rpm_put: + pm_runtime_mark_last_busy(ov5645->dev); + pm_runtime_put_autosuspend(ov5645->dev); + return ret; +} static const struct v4l2_subdev_video_ops ov5645_video_ops = { .s_stream = ov5645_s_stream, @@ -1046,7 +1032,6 @@ static const struct v4l2_subdev_pad_ops ov5645_subdev_pad_ops = { }; static const struct v4l2_subdev_ops ov5645_subdev_ops = { - .core = &ov5645_core_ops, .video = &ov5645_video_ops, .pad = &ov5645_subdev_pad_ops, }; @@ -1090,7 +1075,7 @@ static int ov5645_probe(struct i2c_client *client) } /* get system clock (xclk) */ - ov5645->xclk = devm_clk_get(dev, "xclk"); + ov5645->xclk = devm_clk_get(dev, NULL); if (IS_ERR(ov5645->xclk)) { dev_err(dev, "could not get xclk"); return PTR_ERR(ov5645->xclk); @@ -1188,11 +1173,9 @@ static int ov5645_probe(struct i2c_client *client) goto free_ctrl; } - ret = ov5645_s_power(&ov5645->sd, true); - if (ret < 0) { - dev_err(dev, "could not power up OV5645\n"); + ret = ov5645_set_power_on(dev); + if (ret) goto free_entity; - } ret = ov5645_read_reg(ov5645, OV5645_CHIP_ID_HIGH, &chip_id_high); if (ret < 0 || chip_id_high != OV5645_CHIP_ID_HIGH_BYTE) { @@ -1233,20 +1216,30 @@ static int ov5645_probe(struct i2c_client *client) goto power_down; } - ov5645_s_power(&ov5645->sd, false); + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + + ov5645_entity_init_cfg(&ov5645->sd, NULL); ret = v4l2_async_register_subdev(&ov5645->sd); if (ret < 0) { dev_err(dev, "could not register v4l2 device\n"); - goto free_entity; + goto err_pm_runtime; } - ov5645_entity_init_cfg(&ov5645->sd, NULL); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); return 0; +err_pm_runtime: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); power_down: - ov5645_s_power(&ov5645->sd, false); + ov5645_set_power_off(dev); free_entity: media_entity_cleanup(&ov5645->sd.entity); free_ctrl: @@ -1264,6 +1257,10 @@ static void ov5645_remove(struct i2c_client *client) v4l2_async_unregister_subdev(&ov5645->sd); media_entity_cleanup(&ov5645->sd.entity); v4l2_ctrl_handler_free(&ov5645->ctrls); + pm_runtime_disable(ov5645->dev); + if (!pm_runtime_status_suspended(ov5645->dev)) + ov5645_set_power_off(ov5645->dev); + pm_runtime_set_suspended(ov5645->dev); mutex_destroy(&ov5645->power_lock); } @@ -1279,10 +1276,15 @@ static const struct of_device_id ov5645_of_match[] = { }; MODULE_DEVICE_TABLE(of, ov5645_of_match); +static const struct dev_pm_ops ov5645_pm_ops = { + SET_RUNTIME_PM_OPS(ov5645_set_power_off, ov5645_set_power_on, NULL) +}; + static struct i2c_driver ov5645_i2c_driver = { .driver = { .of_match_table = ov5645_of_match, .name = "ov5645", + .pm = &ov5645_pm_ops, }, .probe_new = ov5645_probe, .remove = ov5645_remove, diff --git a/drivers/media/i2c/ov5648.c b/drivers/media/i2c/ov5648.c index 84604ea7bdf9..17465fcf28e3 100644 --- a/drivers/media/i2c/ov5648.c +++ b/drivers/media/i2c/ov5648.c @@ -2597,6 +2597,7 @@ static void ov5648_remove(struct i2c_client *client) v4l2_ctrl_handler_free(&sensor->ctrls.handler); mutex_destroy(&sensor->mutex); media_entity_cleanup(&subdev->entity); + v4l2_fwnode_endpoint_free(&sensor->endpoint); } static const struct dev_pm_ops ov5648_pm_ops = { diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index a97ec132ba3a..e3c3bed69ad6 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -156,6 +156,7 @@ struct ov5693_device { struct gpio_desc *reset; struct gpio_desc *powerdown; + struct gpio_desc *privacy_led; struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; struct clk *xvclk; @@ -789,6 +790,7 @@ static int ov5693_sensor_init(struct ov5693_device *ov5693) static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) { + gpiod_set_value_cansleep(ov5693->privacy_led, 0); gpiod_set_value_cansleep(ov5693->reset, 1); gpiod_set_value_cansleep(ov5693->powerdown, 1); @@ -818,6 +820,7 @@ static int ov5693_sensor_powerup(struct ov5693_device *ov5693) gpiod_set_value_cansleep(ov5693->powerdown, 0); gpiod_set_value_cansleep(ov5693->reset, 0); + gpiod_set_value_cansleep(ov5693->privacy_led, 1); usleep_range(5000, 7500); @@ -1325,6 +1328,13 @@ static int ov5693_configure_gpios(struct ov5693_device *ov5693) return PTR_ERR(ov5693->powerdown); } + ov5693->privacy_led = devm_gpiod_get_optional(ov5693->dev, "privacy-led", + GPIOD_OUT_LOW); + if (IS_ERR(ov5693->privacy_led)) { + dev_err(ov5693->dev, "Error fetching privacy-led GPIO\n"); + return PTR_ERR(ov5693->privacy_led); + } + return 0; } diff --git a/drivers/media/i2c/ov6650.c b/drivers/media/i2c/ov6650.c index 18f041e985b7..4c0ea2ae671b 100644 --- a/drivers/media/i2c/ov6650.c +++ b/drivers/media/i2c/ov6650.c @@ -1025,8 +1025,7 @@ static const struct v4l2_subdev_internal_ops ov6650_internal_ops = { /* * i2c_driver function */ -static int ov6650_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ov6650_probe(struct i2c_client *client) { struct ov6650 *priv; int ret; @@ -1114,7 +1113,7 @@ static struct i2c_driver ov6650_i2c_driver = { .driver = { .name = "ov6650", }, - .probe = ov6650_probe, + .probe_new = ov6650_probe, .remove = ov6650_remove, .id_table = ov6650_id, }; diff --git a/drivers/media/i2c/ov7640.c b/drivers/media/i2c/ov7640.c index 5e2d67f0f9f2..e6751d5cc64b 100644 --- a/drivers/media/i2c/ov7640.c +++ b/drivers/media/i2c/ov7640.c @@ -42,8 +42,7 @@ static int write_regs(struct i2c_client *client, static const struct v4l2_subdev_ops ov7640_ops; -static int ov7640_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ov7640_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct v4l2_subdev *sd; @@ -87,7 +86,7 @@ static struct i2c_driver ov7640_driver = { .driver = { .name = "ov7640", }, - .probe = ov7640_probe, + .probe_new = ov7640_probe, .remove = ov7640_remove, .id_table = ov7640_id, }; diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c index 4b9b156b53c7..11d3bef65d43 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -15,7 +15,6 @@ #include <linux/i2c.h> #include <linux/delay.h> #include <linux/videodev2.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c index efa18d026ac3..cf8384e09413 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -2110,17 +2110,18 @@ static int ov8856_set_stream(struct v4l2_subdev *sd, int enable) return ret; } -static int __ov8856_power_on(struct ov8856 *ov8856) +static int ov8856_power_on(struct device *dev) { - struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov8856 *ov8856 = to_ov8856(sd); int ret; - if (is_acpi_node(dev_fwnode(&client->dev))) + if (is_acpi_node(dev_fwnode(dev))) return 0; ret = clk_prepare_enable(ov8856->xvclk); if (ret < 0) { - dev_err(&client->dev, "failed to enable xvclk\n"); + dev_err(dev, "failed to enable xvclk\n"); return ret; } @@ -2132,7 +2133,7 @@ static int __ov8856_power_on(struct ov8856 *ov8856) ret = regulator_bulk_enable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); if (ret < 0) { - dev_err(&client->dev, "failed to enable regulators\n"); + dev_err(dev, "failed to enable regulators\n"); goto disable_clk; } @@ -2148,17 +2149,20 @@ disable_clk: return ret; } -static void __ov8856_power_off(struct ov8856 *ov8856) +static int ov8856_power_off(struct device *dev) { - struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov8856 *ov8856 = to_ov8856(sd); - if (is_acpi_node(dev_fwnode(&client->dev))) - return; + if (is_acpi_node(dev_fwnode(dev))) + return 0; gpiod_set_value_cansleep(ov8856->reset_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); clk_disable_unprepare(ov8856->xvclk); + + return 0; } static int __maybe_unused ov8856_suspend(struct device *dev) @@ -2170,7 +2174,7 @@ static int __maybe_unused ov8856_suspend(struct device *dev) if (ov8856->streaming) ov8856_stop_streaming(ov8856); - __ov8856_power_off(ov8856); + ov8856_power_off(dev); mutex_unlock(&ov8856->mutex); return 0; @@ -2184,7 +2188,7 @@ static int __maybe_unused ov8856_resume(struct device *dev) mutex_lock(&ov8856->mutex); - __ov8856_power_on(ov8856); + ov8856_power_on(dev); if (ov8856->streaming) { ret = ov8856_start_streaming(ov8856); if (ret) { @@ -2451,7 +2455,7 @@ static void ov8856_remove(struct i2c_client *client) pm_runtime_disable(&client->dev); mutex_destroy(&ov8856->mutex); - __ov8856_power_off(ov8856); + ov8856_power_off(&client->dev); } static int ov8856_probe(struct i2c_client *client) @@ -2475,7 +2479,7 @@ static int ov8856_probe(struct i2c_client *client) full_power = acpi_dev_state_d0(&client->dev); if (full_power) { - ret = __ov8856_power_on(ov8856); + ret = ov8856_power_on(&client->dev); if (ret) { dev_err(&client->dev, "failed to power on\n"); return ret; @@ -2531,13 +2535,14 @@ probe_error_v4l2_ctrl_handler_free: mutex_destroy(&ov8856->mutex); probe_power_off: - __ov8856_power_off(ov8856); + ov8856_power_off(&client->dev); return ret; } static const struct dev_pm_ops ov8856_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ov8856_suspend, ov8856_resume) + SET_RUNTIME_PM_OPS(ov8856_power_off, ov8856_power_on, NULL) }; #ifdef CONFIG_ACPI diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index df144a2f6eda..37a55d53af56 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -11,8 +11,10 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> #include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> @@ -21,6 +23,13 @@ #define OV9282_MODE_STANDBY 0x00 #define OV9282_MODE_STREAMING 0x01 +#define OV9282_REG_PLL_CTRL_0D 0x030d +#define OV9282_PLL_CTRL_0D_RAW8 0x60 +#define OV9282_PLL_CTRL_0D_RAW10 0x50 + +#define OV9282_REG_TIMING_HTS 0x380c +#define OV9282_TIMING_HTS_MAX 0x7fff + /* Lines per frame */ #define OV9282_REG_LPFR 0x380e @@ -45,6 +54,17 @@ /* Group hold register */ #define OV9282_REG_HOLD 0x3308 +#define OV9282_REG_ANA_CORE_2 0x3662 +#define OV9282_ANA_CORE2_RAW8 0x07 +#define OV9282_ANA_CORE2_RAW10 0x05 + +#define OV9282_REG_TIMING_FORMAT_1 0x3820 +#define OV9282_REG_TIMING_FORMAT_2 0x3821 +#define OV9282_FLIP_BIT BIT(2) + +#define OV9282_REG_MIPI_CTRL00 0x4800 +#define OV9282_GATED_CLOCK BIT(5) + /* Input clock rate */ #define OV9282_INCLK_RATE 24000000 @@ -52,9 +72,34 @@ #define OV9282_LINK_FREQ 400000000 #define OV9282_NUM_DATA_LANES 2 +/* Pixel rate */ +#define OV9282_PIXEL_RATE_10BIT (OV9282_LINK_FREQ * 2 * \ + OV9282_NUM_DATA_LANES / 10) +#define OV9282_PIXEL_RATE_8BIT (OV9282_LINK_FREQ * 2 * \ + OV9282_NUM_DATA_LANES / 8) + +/* + * OV9282 native and active pixel array size. + * 8 dummy rows/columns on each edge of a 1280x800 active array + */ +#define OV9282_NATIVE_WIDTH 1296U +#define OV9282_NATIVE_HEIGHT 816U +#define OV9282_PIXEL_ARRAY_LEFT 8U +#define OV9282_PIXEL_ARRAY_TOP 8U +#define OV9282_PIXEL_ARRAY_WIDTH 1280U +#define OV9282_PIXEL_ARRAY_HEIGHT 800U + #define OV9282_REG_MIN 0x00 #define OV9282_REG_MAX 0xfffff +static const char * const ov9282_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV9282_NUM_SUPPLIES ARRAY_SIZE(ov9282_supply_names) + /** * struct ov9282_reg - ov9282 sensor register * @address: Register address @@ -79,25 +124,24 @@ struct ov9282_reg_list { * struct ov9282_mode - ov9282 sensor mode structure * @width: Frame width * @height: Frame height - * @code: Format code - * @hblank: Horizontal blanking in lines + * @hblank_min: Minimum horizontal blanking in lines for non-continuous[0] and + * continuous[1] clock modes * @vblank: Vertical blanking in lines * @vblank_min: Minimum vertical blanking in lines * @vblank_max: Maximum vertical blanking in lines - * @pclk: Sensor pixel clock * @link_freq_idx: Link frequency index + * @crop: on-sensor cropping for this mode * @reg_list: Register list for sensor mode */ struct ov9282_mode { u32 width; u32 height; - u32 code; - u32 hblank; + u32 hblank_min[2]; u32 vblank; u32 vblank_min; u32 vblank_max; - u64 pclk; u32 link_freq_idx; + struct v4l2_rect crop; struct ov9282_reg_list reg_list; }; @@ -109,15 +153,18 @@ struct ov9282_mode { * @pad: Media pad. Only one pad supported * @reset_gpio: Sensor reset gpio * @inclk: Sensor input clock + * @supplies: Regulator supplies for the sensor * @ctrl_handler: V4L2 control handler * @link_freq_ctrl: Pointer to link frequency control - * @pclk_ctrl: Pointer to pixel clock control * @hblank_ctrl: Pointer to horizontal blanking control * @vblank_ctrl: Pointer to vertical blanking control * @exp_ctrl: Pointer to exposure control * @again_ctrl: Pointer to analog gain control + * @pixel_rate: Pointer to pixel rate control * @vblank: Vertical blanking in lines + * @noncontinuous_clock: Selection of CSI2 noncontinuous clock mode * @cur_mode: Pointer to current selected sensor mode + * @code: Mbus code currently selected * @mutex: Mutex for serializing sensor controls * @streaming: Flag indicating streaming state */ @@ -128,17 +175,20 @@ struct ov9282 { struct media_pad pad; struct gpio_desc *reset_gpio; struct clk *inclk; + struct regulator_bulk_data supplies[OV9282_NUM_SUPPLIES]; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl *link_freq_ctrl; - struct v4l2_ctrl *pclk_ctrl; struct v4l2_ctrl *hblank_ctrl; struct v4l2_ctrl *vblank_ctrl; struct { struct v4l2_ctrl *exp_ctrl; struct v4l2_ctrl *again_ctrl; }; + struct v4l2_ctrl *pixel_rate; u32 vblank; + bool noncontinuous_clock; const struct ov9282_mode *cur_mode; + u32 code; struct mutex mutex; bool streaming; }; @@ -147,10 +197,15 @@ static const s64 link_freq[] = { OV9282_LINK_FREQ, }; -/* Sensor mode registers */ -static const struct ov9282_reg mode_1280x720_regs[] = { +/* + * Common registers + * + * Note: Do NOT include a software reset (0x0103, 0x01) in any of these + * register arrays as some settings are written as part of ov9282_power_on, + * and the reset will clear them. + */ +static const struct ov9282_reg common_regs[] = { {0x0302, 0x32}, - {0x030d, 0x50}, {0x030e, 0x02}, {0x3001, 0x00}, {0x3004, 0x00}, @@ -163,14 +218,10 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x3030, 0x10}, {0x3039, 0x32}, {0x303a, 0x00}, - {0x3500, 0x00}, - {0x3501, 0x5f}, - {0x3502, 0x1e}, {0x3503, 0x08}, {0x3505, 0x8c}, {0x3507, 0x03}, {0x3508, 0x00}, - {0x3509, 0x10}, {0x3610, 0x80}, {0x3611, 0xa0}, {0x3620, 0x6e}, @@ -183,13 +234,85 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x372d, 0x22}, {0x3731, 0x80}, {0x3732, 0x30}, - {0x3778, 0x00}, {0x377d, 0x22}, {0x3788, 0x02}, {0x3789, 0xa4}, {0x378a, 0x00}, {0x378b, 0x4a}, {0x3799, 0x20}, + {0x3881, 0x42}, + {0x38a8, 0x02}, + {0x38a9, 0x80}, + {0x38b1, 0x00}, + {0x38c4, 0x00}, + {0x38c5, 0xc0}, + {0x38c6, 0x04}, + {0x38c7, 0x80}, + {0x3920, 0xff}, + {0x4010, 0x40}, + {0x4043, 0x40}, + {0x4307, 0x30}, + {0x4317, 0x00}, + {0x4501, 0x00}, + {0x450a, 0x08}, + {0x4601, 0x04}, + {0x470f, 0x00}, + {0x4f07, 0x00}, + {0x5000, 0x9f}, + {0x5001, 0x00}, + {0x5e00, 0x00}, + {0x5d00, 0x07}, + {0x5d01, 0x00}, + {0x0101, 0x01}, + {0x1000, 0x03}, + {0x5a08, 0x84}, +}; + +static struct ov9282_reg_list common_regs_list = { + .num_of_regs = ARRAY_SIZE(common_regs), + .regs = common_regs, +}; + +#define MODE_1280_800 0 +#define MODE_1280_720 1 +#define MODE_640_400 2 + +#define DEFAULT_MODE MODE_1280_720 + +/* Sensor mode registers */ +static const struct ov9282_reg mode_1280x800_regs[] = { + {0x3778, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x05}, + {0x3805, 0x0f}, + {0x3806, 0x03}, + {0x3807, 0x2f}, + {0x3808, 0x05}, + {0x3809, 0x00}, + {0x380a, 0x03}, + {0x380b, 0x20}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x08}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x40}, + {0x3821, 0x00}, + {0x4003, 0x40}, + {0x4008, 0x04}, + {0x4009, 0x0b}, + {0x400c, 0x00}, + {0x400d, 0x07}, + {0x4507, 0x00}, + {0x4509, 0x00}, +}; + +static const struct ov9282_reg mode_1280x720_regs[] = { + {0x3778, 0x00}, {0x3800, 0x00}, {0x3801, 0x00}, {0x3802, 0x00}, @@ -202,10 +325,6 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x3809, 0x00}, {0x380a, 0x02}, {0x380b, 0xd0}, - {0x380c, 0x05}, - {0x380d, 0xfa}, - {0x380e, 0x06}, - {0x380f, 0xce}, {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, @@ -214,56 +333,107 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x3815, 0x11}, {0x3820, 0x3c}, {0x3821, 0x84}, - {0x3881, 0x42}, - {0x38a8, 0x02}, - {0x38a9, 0x80}, - {0x38b1, 0x00}, - {0x38c4, 0x00}, - {0x38c5, 0xc0}, - {0x38c6, 0x04}, - {0x38c7, 0x80}, - {0x3920, 0xff}, {0x4003, 0x40}, {0x4008, 0x02}, {0x4009, 0x05}, {0x400c, 0x00}, {0x400d, 0x03}, - {0x4010, 0x40}, - {0x4043, 0x40}, - {0x4307, 0x30}, - {0x4317, 0x00}, - {0x4501, 0x00}, {0x4507, 0x00}, {0x4509, 0x80}, - {0x450a, 0x08}, - {0x4601, 0x04}, - {0x470f, 0x00}, - {0x4f07, 0x00}, - {0x4800, 0x20}, - {0x5000, 0x9f}, - {0x5001, 0x00}, - {0x5e00, 0x00}, - {0x5d00, 0x07}, - {0x5d01, 0x00}, - {0x0101, 0x01}, - {0x1000, 0x03}, - {0x5a08, 0x84}, +}; + +static const struct ov9282_reg mode_640x400_regs[] = { + {0x3778, 0x10}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x05}, + {0x3805, 0x0f}, + {0x3806, 0x03}, + {0x3807, 0x2f}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0x90}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3812, 0x00}, + {0x3813, 0x04}, + {0x3814, 0x31}, + {0x3815, 0x22}, + {0x3820, 0x60}, + {0x3821, 0x01}, + {0x4008, 0x02}, + {0x4009, 0x05}, + {0x400c, 0x00}, + {0x400d, 0x03}, + {0x4507, 0x03}, + {0x4509, 0x80}, }; /* Supported sensor mode configurations */ -static const struct ov9282_mode supported_mode = { - .width = 1280, - .height = 720, - .hblank = 250, - .vblank = 1022, - .vblank_min = 151, - .vblank_max = 51540, - .pclk = 160000000, - .link_freq_idx = 0, - .code = MEDIA_BUS_FMT_Y10_1X10, - .reg_list = { - .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), - .regs = mode_1280x720_regs, +static const struct ov9282_mode supported_modes[] = { + [MODE_1280_800] = { + .width = 1280, + .height = 800, + .hblank_min = { 250, 176 }, + .vblank = 1022, + .vblank_min = 110, + .vblank_max = 51540, + .link_freq_idx = 0, + .crop = { + .left = OV9282_PIXEL_ARRAY_LEFT, + .top = OV9282_PIXEL_ARRAY_TOP, + .width = 1280, + .height = 800 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x800_regs), + .regs = mode_1280x800_regs, + }, + }, + [MODE_1280_720] = { + .width = 1280, + .height = 720, + .hblank_min = { 250, 176 }, + .vblank = 1022, + .vblank_min = 41, + .vblank_max = 51540, + .link_freq_idx = 0, + .crop = { + /* + * Note that this mode takes the top 720 lines from the + * 800 of the sensor. It does not take a middle crop. + */ + .left = OV9282_PIXEL_ARRAY_LEFT, + .top = OV9282_PIXEL_ARRAY_TOP, + .width = 1280, + .height = 720 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, + }, + }, + [MODE_640_400] = { + .width = 640, + .height = 400, + .hblank_min = { 890, 816 }, + .vblank = 1022, + .vblank_min = 22, + .vblank_max = 51540, + .link_freq_idx = 0, + .crop = { + .left = OV9282_PIXEL_ARRAY_LEFT, + .top = OV9282_PIXEL_ARRAY_TOP, + .width = 1280, + .height = 800 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_640x400_regs), + .regs = mode_640x400_regs, + }, }, }; @@ -373,19 +543,33 @@ static int ov9282_write_regs(struct ov9282 *ov9282, * ov9282_update_controls() - Update control ranges based on streaming mode * @ov9282: pointer to ov9282 device * @mode: pointer to ov9282_mode sensor mode + * @fmt: pointer to the requested mode * * Return: 0 if successful, error code otherwise. */ static int ov9282_update_controls(struct ov9282 *ov9282, - const struct ov9282_mode *mode) + const struct ov9282_mode *mode, + const struct v4l2_subdev_format *fmt) { + u32 hblank_min; + s64 pixel_rate; int ret; ret = __v4l2_ctrl_s_ctrl(ov9282->link_freq_ctrl, mode->link_freq_idx); if (ret) return ret; - ret = __v4l2_ctrl_s_ctrl(ov9282->hblank_ctrl, mode->hblank); + pixel_rate = (fmt->format.code == MEDIA_BUS_FMT_Y10_1X10) ? + OV9282_PIXEL_RATE_10BIT : OV9282_PIXEL_RATE_8BIT; + ret = __v4l2_ctrl_modify_range(ov9282->pixel_rate, pixel_rate, + pixel_rate, 1, pixel_rate); + if (ret) + return ret; + + hblank_min = mode->hblank_min[ov9282->noncontinuous_clock ? 0 : 1]; + ret = __v4l2_ctrl_modify_range(ov9282->hblank_ctrl, hblank_min, + OV9282_TIMING_HTS_MAX - mode->width, 1, + hblank_min); if (ret) return ret; @@ -403,22 +587,15 @@ static int ov9282_update_controls(struct ov9282 *ov9282, */ static int ov9282_update_exp_gain(struct ov9282 *ov9282, u32 exposure, u32 gain) { - u32 lpfr; int ret; - lpfr = ov9282->vblank + ov9282->cur_mode->height; - - dev_dbg(ov9282->dev, "Set exp %u, analog gain %u, lpfr %u", - exposure, gain, lpfr); + dev_dbg(ov9282->dev, "Set exp %u, analog gain %u", + exposure, gain); ret = ov9282_write_reg(ov9282, OV9282_REG_HOLD, 1, 1); if (ret) return ret; - ret = ov9282_write_reg(ov9282, OV9282_REG_LPFR, 2, lpfr); - if (ret) - goto error_release_group_hold; - ret = ov9282_write_reg(ov9282, OV9282_REG_EXPOSURE, 3, exposure << 4); if (ret) goto error_release_group_hold; @@ -431,6 +608,40 @@ error_release_group_hold: return ret; } +static int ov9282_set_ctrl_hflip(struct ov9282 *ov9282, int value) +{ + u32 current_val; + int ret = ov9282_read_reg(ov9282, OV9282_REG_TIMING_FORMAT_2, 1, + ¤t_val); + if (ret) + return ret; + + if (value) + current_val |= OV9282_FLIP_BIT; + else + current_val &= ~OV9282_FLIP_BIT; + + return ov9282_write_reg(ov9282, OV9282_REG_TIMING_FORMAT_2, 1, + current_val); +} + +static int ov9282_set_ctrl_vflip(struct ov9282 *ov9282, int value) +{ + u32 current_val; + int ret = ov9282_read_reg(ov9282, OV9282_REG_TIMING_FORMAT_1, 1, + ¤t_val); + if (ret) + return ret; + + if (value) + current_val |= OV9282_FLIP_BIT; + else + current_val &= ~OV9282_FLIP_BIT; + + return ov9282_write_reg(ov9282, OV9282_REG_TIMING_FORMAT_1, 1, + current_val); +} + /** * ov9282_set_ctrl() - Set subdevice control * @ctrl: pointer to v4l2_ctrl structure @@ -449,6 +660,7 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) container_of(ctrl->handler, struct ov9282, ctrl_handler); u32 analog_gain; u32 exposure; + u32 lpfr; int ret; switch (ctrl->id) { @@ -466,11 +678,14 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) OV9282_EXPOSURE_OFFSET, 1, OV9282_EXPOSURE_DEFAULT); break; - case V4L2_CID_EXPOSURE: - /* Set controls only if sensor is in power on state */ - if (!pm_runtime_get_if_in_use(ov9282->dev)) - return 0; + } + + /* Set controls only if sensor is in power on state */ + if (!pm_runtime_get_if_in_use(ov9282->dev)) + return 0; + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: exposure = ctrl->val; analog_gain = ov9282->again_ctrl->val; @@ -478,15 +693,28 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) exposure, analog_gain); ret = ov9282_update_exp_gain(ov9282, exposure, analog_gain); - - pm_runtime_put(ov9282->dev); - + break; + case V4L2_CID_VBLANK: + lpfr = ov9282->vblank + ov9282->cur_mode->height; + ret = ov9282_write_reg(ov9282, OV9282_REG_LPFR, 2, lpfr); + break; + case V4L2_CID_HFLIP: + ret = ov9282_set_ctrl_hflip(ov9282, ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = ov9282_set_ctrl_vflip(ov9282, ctrl->val); + break; + case V4L2_CID_HBLANK: + ret = ov9282_write_reg(ov9282, OV9282_REG_TIMING_HTS, 2, + (ctrl->val + ov9282->cur_mode->width) >> 1); break; default: dev_err(ov9282->dev, "Invalid control %d", ctrl->id); ret = -EINVAL; } + pm_runtime_put(ov9282->dev); + return ret; } @@ -507,10 +735,16 @@ static int ov9282_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { - if (code->index > 0) + switch (code->index) { + case 0: + code->code = MEDIA_BUS_FMT_Y10_1X10; + break; + case 1: + code->code = MEDIA_BUS_FMT_Y8_1X8; + break; + default: return -EINVAL; - - code->code = supported_mode.code; + } return 0; } @@ -527,15 +761,16 @@ static int ov9282_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_size_enum *fsize) { - if (fsize->index > 0) + if (fsize->index >= ARRAY_SIZE(supported_modes)) return -EINVAL; - if (fsize->code != supported_mode.code) + if (fsize->code != MEDIA_BUS_FMT_Y10_1X10 && + fsize->code != MEDIA_BUS_FMT_Y8_1X8) return -EINVAL; - fsize->min_width = supported_mode.width; + fsize->min_width = supported_modes[fsize->index].width; fsize->max_width = fsize->min_width; - fsize->min_height = supported_mode.height; + fsize->min_height = supported_modes[fsize->index].height; fsize->max_height = fsize->min_height; return 0; @@ -546,15 +781,17 @@ static int ov9282_enum_frame_size(struct v4l2_subdev *sd, * from selected sensor mode * @ov9282: pointer to ov9282 device * @mode: pointer to ov9282_mode sensor mode + * @code: mbus code to be stored * @fmt: V4L2 sub-device format need to be filled */ static void ov9282_fill_pad_format(struct ov9282 *ov9282, const struct ov9282_mode *mode, + u32 code, struct v4l2_subdev_format *fmt) { fmt->format.width = mode->width; fmt->format.height = mode->height; - fmt->format.code = mode->code; + fmt->format.code = code; fmt->format.field = V4L2_FIELD_NONE; fmt->format.colorspace = V4L2_COLORSPACE_RAW; fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; @@ -584,7 +821,8 @@ static int ov9282_get_pad_format(struct v4l2_subdev *sd, framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); fmt->format = *framefmt; } else { - ov9282_fill_pad_format(ov9282, ov9282->cur_mode, fmt); + ov9282_fill_pad_format(ov9282, ov9282->cur_mode, ov9282->code, + fmt); } mutex_unlock(&ov9282->mutex); @@ -606,12 +844,22 @@ static int ov9282_set_pad_format(struct v4l2_subdev *sd, { struct ov9282 *ov9282 = to_ov9282(sd); const struct ov9282_mode *mode; + u32 code; int ret = 0; mutex_lock(&ov9282->mutex); - mode = &supported_mode; - ov9282_fill_pad_format(ov9282, mode, fmt); + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, + fmt->format.height); + if (fmt->format.code == MEDIA_BUS_FMT_Y8_1X8) + code = MEDIA_BUS_FMT_Y8_1X8; + else + code = MEDIA_BUS_FMT_Y10_1X10; + + ov9282_fill_pad_format(ov9282, mode, code, fmt); if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { struct v4l2_mbus_framefmt *framefmt; @@ -619,9 +867,11 @@ static int ov9282_set_pad_format(struct v4l2_subdev *sd, framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); *framefmt = fmt->format; } else { - ret = ov9282_update_controls(ov9282, mode); - if (!ret) + ret = ov9282_update_controls(ov9282, mode, fmt); + if (!ret) { ov9282->cur_mode = mode; + ov9282->code = code; + } } mutex_unlock(&ov9282->mutex); @@ -643,11 +893,64 @@ static int ov9282_init_pad_cfg(struct v4l2_subdev *sd, struct v4l2_subdev_format fmt = { 0 }; fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; - ov9282_fill_pad_format(ov9282, &supported_mode, &fmt); + ov9282_fill_pad_format(ov9282, &supported_modes[DEFAULT_MODE], + ov9282->code, &fmt); return ov9282_set_pad_format(sd, sd_state, &fmt); } +static const struct v4l2_rect * +__ov9282_get_pad_crop(struct ov9282 *ov9282, + struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&ov9282->sd, sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ov9282->cur_mode->crop; + } + + return NULL; +} + +static int ov9282_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct ov9282 *ov9282 = to_ov9282(sd); + + mutex_lock(&ov9282->mutex); + sel->r = *__ov9282_get_pad_crop(ov9282, sd_state, sel->pad, + sel->which); + mutex_unlock(&ov9282->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = OV9282_NATIVE_WIDTH; + sel->r.height = OV9282_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = OV9282_PIXEL_ARRAY_TOP; + sel->r.left = OV9282_PIXEL_ARRAY_LEFT; + sel->r.width = OV9282_PIXEL_ARRAY_WIDTH; + sel->r.height = OV9282_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + /** * ov9282_start_streaming() - Start sensor stream * @ov9282: pointer to ov9282 device @@ -656,9 +959,34 @@ static int ov9282_init_pad_cfg(struct v4l2_subdev *sd, */ static int ov9282_start_streaming(struct ov9282 *ov9282) { + const struct ov9282_reg bitdepth_regs[2][2] = { + { + {OV9282_REG_PLL_CTRL_0D, OV9282_PLL_CTRL_0D_RAW10}, + {OV9282_REG_ANA_CORE_2, OV9282_ANA_CORE2_RAW10}, + }, { + {OV9282_REG_PLL_CTRL_0D, OV9282_PLL_CTRL_0D_RAW8}, + {OV9282_REG_ANA_CORE_2, OV9282_ANA_CORE2_RAW8}, + } + }; const struct ov9282_reg_list *reg_list; + int bitdepth_index; int ret; + /* Write common registers */ + ret = ov9282_write_regs(ov9282, common_regs_list.regs, + common_regs_list.num_of_regs); + if (ret) { + dev_err(ov9282->dev, "fail to write common registers"); + return ret; + } + + bitdepth_index = ov9282->code == MEDIA_BUS_FMT_Y10_1X10 ? 0 : 1; + ret = ov9282_write_regs(ov9282, bitdepth_regs[bitdepth_index], 2); + if (ret) { + dev_err(ov9282->dev, "fail to write bitdepth regs"); + return ret; + } + /* Write sensor mode registers */ reg_list = &ov9282->cur_mode->reg_list; ret = ov9282_write_regs(ov9282, reg_list->regs, reg_list->num_of_regs); @@ -767,6 +1095,18 @@ static int ov9282_detect(struct ov9282 *ov9282) return 0; } +static int ov9282_configure_regulators(struct ov9282 *ov9282) +{ + unsigned int i; + + for (i = 0; i < OV9282_NUM_SUPPLIES; i++) + ov9282->supplies[i].supply = ov9282_supply_names[i]; + + return devm_regulator_bulk_get(ov9282->dev, + OV9282_NUM_SUPPLIES, + ov9282->supplies); +} + /** * ov9282_parse_hw_config() - Parse HW configuration and check if supported * @ov9282: pointer to ov9282 device @@ -803,6 +1143,12 @@ static int ov9282_parse_hw_config(struct ov9282 *ov9282) return PTR_ERR(ov9282->inclk); } + ret = ov9282_configure_regulators(ov9282); + if (ret) { + dev_err(ov9282->dev, "Failed to get power regulators\n"); + return ret; + } + rate = clk_get_rate(ov9282->inclk); if (rate != OV9282_INCLK_RATE) { dev_err(ov9282->dev, "inclk frequency mismatch"); @@ -818,6 +1164,9 @@ static int ov9282_parse_hw_config(struct ov9282 *ov9282) if (ret) return ret; + ov9282->noncontinuous_clock = + bus_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV9282_NUM_DATA_LANES) { dev_err(ov9282->dev, "number of CSI2 data lanes %d is not supported", @@ -845,6 +1194,11 @@ done_endpoint_free: } /* V4l2 subdevice ops */ +static const struct v4l2_subdev_core_ops ov9282_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + static const struct v4l2_subdev_video_ops ov9282_video_ops = { .s_stream = ov9282_set_stream, }; @@ -855,9 +1209,11 @@ static const struct v4l2_subdev_pad_ops ov9282_pad_ops = { .enum_frame_size = ov9282_enum_frame_size, .get_fmt = ov9282_get_pad_format, .set_fmt = ov9282_set_pad_format, + .get_selection = ov9282_get_selection, }; static const struct v4l2_subdev_ops ov9282_subdev_ops = { + .core = &ov9282_core_ops, .video = &ov9282_video_ops, .pad = &ov9282_pad_ops, }; @@ -874,6 +1230,12 @@ static int ov9282_power_on(struct device *dev) struct ov9282 *ov9282 = to_ov9282(sd); int ret; + ret = regulator_bulk_enable(OV9282_NUM_SUPPLIES, ov9282->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + return ret; + } + usleep_range(400, 600); gpiod_set_value_cansleep(ov9282->reset_gpio, 1); @@ -886,11 +1248,23 @@ static int ov9282_power_on(struct device *dev) usleep_range(400, 600); + ret = ov9282_write_reg(ov9282, OV9282_REG_MIPI_CTRL00, 1, + ov9282->noncontinuous_clock ? + OV9282_GATED_CLOCK : 0); + if (ret) { + dev_err(ov9282->dev, "fail to write MIPI_CTRL00"); + goto error_clk; + } + return 0; +error_clk: + clk_disable_unprepare(ov9282->inclk); error_reset: gpiod_set_value_cansleep(ov9282->reset_gpio, 0); + regulator_bulk_disable(OV9282_NUM_SUPPLIES, ov9282->supplies); + return ret; } @@ -909,6 +1283,8 @@ static int ov9282_power_off(struct device *dev) clk_disable_unprepare(ov9282->inclk); + regulator_bulk_disable(OV9282_NUM_SUPPLIES, ov9282->supplies); + return 0; } @@ -922,10 +1298,12 @@ static int ov9282_init_controls(struct ov9282 *ov9282) { struct v4l2_ctrl_handler *ctrl_hdlr = &ov9282->ctrl_handler; const struct ov9282_mode *mode = ov9282->cur_mode; + struct v4l2_fwnode_device_properties props; + u32 hblank_min; u32 lpfr; int ret; - ret = v4l2_ctrl_handler_init(ctrl_hdlr, 6); + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); if (ret) return ret; @@ -959,12 +1337,18 @@ static int ov9282_init_controls(struct ov9282 *ov9282) mode->vblank_max, 1, mode->vblank); + v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_VFLIP, + 0, 1, 1, 1); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_HFLIP, + 0, 1, 1, 1); + /* Read only controls */ - ov9282->pclk_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, - &ov9282_ctrl_ops, - V4L2_CID_PIXEL_RATE, - mode->pclk, mode->pclk, - 1, mode->pclk); + ov9282->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, + V4L2_CID_PIXEL_RATE, + OV9282_PIXEL_RATE_10BIT, + OV9282_PIXEL_RATE_10BIT, 1, + OV9282_PIXEL_RATE_10BIT); ov9282->link_freq_ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov9282_ctrl_ops, @@ -976,16 +1360,22 @@ static int ov9282_init_controls(struct ov9282 *ov9282) if (ov9282->link_freq_ctrl) ov9282->link_freq_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + hblank_min = mode->hblank_min[ov9282->noncontinuous_clock ? 0 : 1]; ov9282->hblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_HBLANK, - OV9282_REG_MIN, - OV9282_REG_MAX, - 1, mode->hblank); - if (ov9282->hblank_ctrl) - ov9282->hblank_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + hblank_min, + OV9282_TIMING_HTS_MAX - mode->width, + 1, hblank_min); + + ret = v4l2_fwnode_device_parse(ov9282->dev, &props); + if (!ret) { + /* Failure sets ctrl_hdlr->error, which we check afterwards anyway */ + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov9282_ctrl_ops, + &props); + } - if (ctrl_hdlr->error) { + if (ctrl_hdlr->error || ret) { dev_err(ov9282->dev, "control init failed: %d", ctrl_hdlr->error); v4l2_ctrl_handler_free(ctrl_hdlr); @@ -1016,6 +1406,8 @@ static int ov9282_probe(struct i2c_client *client) /* Initialize subdev */ v4l2_i2c_subdev_init(&ov9282->sd, client, &ov9282_subdev_ops); + v4l2_i2c_subdev_set_name(&ov9282->sd, client, + device_get_match_data(ov9282->dev), NULL); ret = ov9282_parse_hw_config(ov9282); if (ret) { @@ -1038,8 +1430,9 @@ static int ov9282_probe(struct i2c_client *client) goto error_power_off; } - /* Set default mode to max resolution */ - ov9282->cur_mode = &supported_mode; + /* Set default mode to first mode */ + ov9282->cur_mode = &supported_modes[DEFAULT_MODE]; + ov9282->code = MEDIA_BUS_FMT_Y10_1X10; ov9282->vblank = ov9282->cur_mode->vblank; ret = ov9282_init_controls(ov9282); @@ -1049,7 +1442,8 @@ static int ov9282_probe(struct i2c_client *client) } /* Initialize subdev */ - ov9282->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov9282->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; ov9282->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; /* Initialize source pad */ @@ -1113,7 +1507,8 @@ static const struct dev_pm_ops ov9282_pm_ops = { }; static const struct of_device_id ov9282_of_match[] = { - { .compatible = "ovti,ov9282" }, + { .compatible = "ovti,ov9281", .data = "ov9281" }, + { .compatible = "ovti,ov9282", .data = "ov9282" }, { } }; diff --git a/drivers/media/i2c/ov9640.c b/drivers/media/i2c/ov9640.c index 8b80be33c5f4..a80fa59bf2ae 100644 --- a/drivers/media/i2c/ov9640.c +++ b/drivers/media/i2c/ov9640.c @@ -682,8 +682,7 @@ static const struct v4l2_subdev_ops ov9640_subdev_ops = { /* * i2c_driver function */ -static int ov9640_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ov9640_probe(struct i2c_client *client) { struct ov9640_priv *priv; int ret; @@ -763,7 +762,7 @@ static struct i2c_driver ov9640_i2c_driver = { .driver = { .name = "ov9640", }, - .probe = ov9640_probe, + .probe_new = ov9640_probe, .remove = ov9640_remove, .id_table = ov9640_id, }; diff --git a/drivers/media/i2c/ov9650.c b/drivers/media/i2c/ov9650.c index 4d458993e6d6..7e7cb1e4520e 100644 --- a/drivers/media/i2c/ov9650.c +++ b/drivers/media/i2c/ov9650.c @@ -10,7 +10,6 @@ */ #include <linux/clk.h> #include <linux/delay.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/kernel.h> @@ -30,7 +29,6 @@ #include <media/v4l2-image-sizes.h> #include <media/v4l2-subdev.h> #include <media/v4l2-mediabus.h> -#include <media/i2c/ov9650.h> static int debug; module_param(debug, int, 0644); @@ -1402,38 +1400,6 @@ static const struct v4l2_subdev_ops ov965x_subdev_ops = { .video = &ov965x_video_ops, }; -/* - * Reset and power down GPIOs configuration - */ -static int ov965x_configure_gpios_pdata(struct ov965x *ov965x, - const struct ov9650_platform_data *pdata) -{ - int ret, i; - int gpios[NUM_GPIOS]; - struct device *dev = regmap_get_device(ov965x->regmap); - - gpios[GPIO_PWDN] = pdata->gpio_pwdn; - gpios[GPIO_RST] = pdata->gpio_reset; - - for (i = 0; i < ARRAY_SIZE(ov965x->gpios); i++) { - int gpio = gpios[i]; - - if (!gpio_is_valid(gpio)) - continue; - ret = devm_gpio_request_one(dev, gpio, - GPIOF_OUT_INIT_HIGH, "OV965X"); - if (ret < 0) - return ret; - v4l2_dbg(1, debug, &ov965x->sd, "set gpio %d to 1\n", gpio); - - gpio_set_value_cansleep(gpio, 1); - gpio_export(gpio, 0); - ov965x->gpios[i] = gpio_to_desc(gpio); - } - - return 0; -} - static int ov965x_configure_gpios(struct ov965x *ov965x) { struct device *dev = regmap_get_device(ov965x->regmap); @@ -1493,7 +1459,6 @@ out: static int ov965x_probe(struct i2c_client *client) { - const struct ov9650_platform_data *pdata = client->dev.platform_data; struct v4l2_subdev *sd; struct ov965x *ov965x; int ret; @@ -1513,17 +1478,7 @@ static int ov965x_probe(struct i2c_client *client) return PTR_ERR(ov965x->regmap); } - if (pdata) { - if (pdata->mclk_frequency == 0) { - dev_err(&client->dev, "MCLK frequency not specified\n"); - return -EINVAL; - } - ov965x->mclk_frequency = pdata->mclk_frequency; - - ret = ov965x_configure_gpios_pdata(ov965x, pdata); - if (ret < 0) - return ret; - } else if (dev_fwnode(&client->dev)) { + if (dev_fwnode(&client->dev)) { ov965x->clk = devm_clk_get(&client->dev, NULL); if (IS_ERR(ov965x->clk)) return PTR_ERR(ov965x->clk); @@ -1534,7 +1489,7 @@ static int ov965x_probe(struct i2c_client *client) return ret; } else { dev_err(&client->dev, - "Neither platform data nor device property specified\n"); + "No device properties specified\n"); return -EINVAL; } diff --git a/drivers/media/i2c/rj54n1cb0c.c b/drivers/media/i2c/rj54n1cb0c.c index 1c3502f34cd3..9db5473daba0 100644 --- a/drivers/media/i2c/rj54n1cb0c.c +++ b/drivers/media/i2c/rj54n1cb0c.c @@ -1297,8 +1297,7 @@ done: return ret; } -static int rj54n1_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int rj54n1_probe(struct i2c_client *client) { struct rj54n1 *rj54n1; struct i2c_adapter *adapter = client->adapter; @@ -1422,7 +1421,7 @@ static struct i2c_driver rj54n1_i2c_driver = { .driver = { .name = "rj54n1cb0c", }, - .probe = rj54n1_probe, + .probe_new = rj54n1_probe, .remove = rj54n1_remove, .id_table = rj54n1_id, }; diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c index d96ba58ce1e5..59b03b0860d5 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-core.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -10,12 +10,11 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/firmware.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/media.h> #include <linux/module.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> #include <linux/regulator/consumer.h> #include <linux/sizes.h> @@ -1347,24 +1346,6 @@ static int s5c73m3_oif_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) return 0; } -static int s5c73m3_gpio_set_value(struct s5c73m3 *priv, int id, u32 val) -{ - if (!gpio_is_valid(priv->gpio[id].gpio)) - return 0; - gpio_set_value(priv->gpio[id].gpio, !!val); - return 1; -} - -static int s5c73m3_gpio_assert(struct s5c73m3 *priv, int id) -{ - return s5c73m3_gpio_set_value(priv, id, priv->gpio[id].level); -} - -static int s5c73m3_gpio_deassert(struct s5c73m3 *priv, int id) -{ - return s5c73m3_gpio_set_value(priv, id, !priv->gpio[id].level); -} - static int __s5c73m3_power_on(struct s5c73m3 *state) { int i, ret; @@ -1386,10 +1367,9 @@ static int __s5c73m3_power_on(struct s5c73m3 *state) v4l2_dbg(1, s5c73m3_dbg, &state->oif_sd, "clock frequency: %ld\n", clk_get_rate(state->clock)); - s5c73m3_gpio_deassert(state, STBY); + gpiod_set_value(state->stby, 0); usleep_range(100, 200); - - s5c73m3_gpio_deassert(state, RSET); + gpiod_set_value(state->reset, 0); usleep_range(50, 100); return 0; @@ -1404,11 +1384,10 @@ static int __s5c73m3_power_off(struct s5c73m3 *state) { int i, ret; - if (s5c73m3_gpio_assert(state, RSET)) - usleep_range(10, 50); - - if (s5c73m3_gpio_assert(state, STBY)) - usleep_range(100, 200); + gpiod_set_value(state->reset, 1); + usleep_range(10, 50); + gpiod_set_value(state->stby, 1); + usleep_range(100, 200); clk_disable_unprepare(state->clock); @@ -1543,58 +1522,10 @@ static const struct v4l2_subdev_ops oif_subdev_ops = { .video = &s5c73m3_oif_video_ops, }; -static int s5c73m3_configure_gpios(struct s5c73m3 *state) -{ - static const char * const gpio_names[] = { - "S5C73M3_STBY", "S5C73M3_RST" - }; - struct i2c_client *c = state->i2c_client; - struct s5c73m3_gpio *g = state->gpio; - int ret, i; - - for (i = 0; i < GPIO_NUM; ++i) { - unsigned int flags = GPIOF_DIR_OUT; - if (g[i].level) - flags |= GPIOF_INIT_HIGH; - ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags, - gpio_names[i]); - if (ret) { - v4l2_err(c, "failed to request gpio %s\n", - gpio_names[i]); - return ret; - } - } - return 0; -} - -static int s5c73m3_parse_gpios(struct s5c73m3 *state) -{ - static const char * const prop_names[] = { - "standby-gpios", "xshutdown-gpios", - }; - struct device *dev = &state->i2c_client->dev; - struct device_node *node = dev->of_node; - int ret, i; - - for (i = 0; i < GPIO_NUM; ++i) { - enum of_gpio_flags of_flags; - - ret = of_get_named_gpio_flags(node, prop_names[i], - 0, &of_flags); - if (ret < 0) { - dev_err(dev, "failed to parse %s DT property\n", - prop_names[i]); - return -EINVAL; - } - state->gpio[i].gpio = ret; - state->gpio[i].level = !(of_flags & OF_GPIO_ACTIVE_LOW); - } - return 0; -} - static int s5c73m3_get_platform_data(struct s5c73m3 *state) { - struct device *dev = &state->i2c_client->dev; + struct i2c_client *c = state->i2c_client; + struct device *dev = &c->dev; const struct s5c73m3_platform_data *pdata = dev->platform_data; struct device_node *node = dev->of_node; struct device_node *node_ep; @@ -1608,8 +1539,6 @@ static int s5c73m3_get_platform_data(struct s5c73m3 *state) } state->mclk_frequency = pdata->mclk_frequency; - state->gpio[STBY] = pdata->gpio_stby; - state->gpio[RSET] = pdata->gpio_reset; return 0; } @@ -1624,9 +1553,17 @@ static int s5c73m3_get_platform_data(struct s5c73m3 *state) state->mclk_frequency); } - ret = s5c73m3_parse_gpios(state); - if (ret < 0) - return -EINVAL; + /* Request GPIO lines asserted */ + state->stby = devm_gpiod_get(dev, "standby", GPIOD_OUT_HIGH); + if (IS_ERR(state->stby)) + return dev_err_probe(dev, PTR_ERR(state->stby), + "failed to request gpio S5C73M3_STBY\n"); + gpiod_set_consumer_name(state->stby, "S5C73M3_STBY"); + state->reset = devm_gpiod_get(dev, "xshutdown", GPIOD_OUT_HIGH); + if (IS_ERR(state->reset)) + return dev_err_probe(dev, PTR_ERR(state->reset), + "failed to request gpio S5C73M3_RST\n"); + gpiod_set_consumer_name(state->reset, "S5C73M3_RST"); node_ep = of_graph_get_next_endpoint(node, NULL); if (!node_ep) { @@ -1708,10 +1645,6 @@ static int s5c73m3_probe(struct i2c_client *client) if (ret < 0) return ret; - ret = s5c73m3_configure_gpios(state); - if (ret) - goto out_err; - for (i = 0; i < S5C73M3_MAX_SUPPLIES; i++) state->supplies[i].supply = s5c73m3_supply_names[i]; diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c index 141ad0ba7f5a..e3543ae384ed 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c @@ -10,7 +10,6 @@ #include <linux/sizes.h> #include <linux/delay.h> #include <linux/firmware.h> -#include <linux/gpio.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/media.h> diff --git a/drivers/media/i2c/s5c73m3/s5c73m3.h b/drivers/media/i2c/s5c73m3/s5c73m3.h index c3fcfdd3ea66..1fc7df41c5ee 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3.h +++ b/drivers/media/i2c/s5c73m3/s5c73m3.h @@ -12,6 +12,7 @@ #include <linux/clk.h> #include <linux/kernel.h> #include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-subdev.h> @@ -351,12 +352,6 @@ struct s5c73m3_ctrls { struct v4l2_ctrl *scene_mode; }; -enum s5c73m3_gpio_id { - STBY, - RSET, - GPIO_NUM, -}; - enum s5c73m3_resolution_types { RES_ISP, RES_JPEG, @@ -383,7 +378,8 @@ struct s5c73m3 { u32 i2c_read_address; struct regulator_bulk_data supplies[S5C73M3_MAX_SUPPLIES]; - struct s5c73m3_gpio gpio[GPIO_NUM]; + struct gpio_desc *stby; + struct gpio_desc *reset; struct clk *clock; diff --git a/drivers/media/i2c/s5k4ecgx.c b/drivers/media/i2c/s5k4ecgx.c deleted file mode 100644 index 3dddcd9dd351..000000000000 --- a/drivers/media/i2c/s5k4ecgx.c +++ /dev/null @@ -1,1032 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Driver for Samsung S5K4ECGX 1/4" 5Mp CMOS Image Sensor SoC - * with an Embedded Image Signal Processor. - * - * Copyright (C) 2012, Linaro, Sangwook Lee <sangwook.lee@linaro.org> - * Copyright (C) 2012, Insignal Co,. Ltd, Homin Lee <suapapa@insignal.co.kr> - * - * Based on s5k6aa and noon010pc30 driver - * Copyright (C) 2011, Samsung Electronics Co., Ltd. - */ - -#include <linux/clk.h> -#include <linux/crc32.h> -#include <linux/ctype.h> -#include <linux/delay.h> -#include <linux/firmware.h> -#include <linux/gpio.h> -#include <linux/i2c.h> -#include <linux/module.h> -#include <linux/regulator/consumer.h> -#include <linux/slab.h> -#include <asm/unaligned.h> - -#include <media/media-entity.h> -#include <media/i2c/s5k4ecgx.h> -#include <media/v4l2-ctrls.h> -#include <media/v4l2-device.h> -#include <media/v4l2-mediabus.h> -#include <media/v4l2-subdev.h> - -static int debug; -module_param(debug, int, 0644); - -#define S5K4ECGX_DRIVER_NAME "s5k4ecgx" -#define S5K4ECGX_FIRMWARE "s5k4ecgx.bin" - -/* Firmware revision information */ -#define REG_FW_REVISION 0x700001a6 -#define REG_FW_VERSION 0x700001a4 -#define S5K4ECGX_REVISION_1_1 0x11 -#define S5K4ECGX_FW_VERSION 0x4ec0 - -/* General purpose parameters */ -#define REG_USER_BRIGHTNESS 0x7000022c -#define REG_USER_CONTRAST 0x7000022e -#define REG_USER_SATURATION 0x70000230 - -#define REG_G_ENABLE_PREV 0x7000023e -#define REG_G_ENABLE_PREV_CHG 0x70000240 -#define REG_G_NEW_CFG_SYNC 0x7000024a -#define REG_G_PREV_IN_WIDTH 0x70000250 -#define REG_G_PREV_IN_HEIGHT 0x70000252 -#define REG_G_PREV_IN_XOFFS 0x70000254 -#define REG_G_PREV_IN_YOFFS 0x70000256 -#define REG_G_CAP_IN_WIDTH 0x70000258 -#define REG_G_CAP_IN_HEIGHT 0x7000025a -#define REG_G_CAP_IN_XOFFS 0x7000025c -#define REG_G_CAP_IN_YOFFS 0x7000025e -#define REG_G_INPUTS_CHANGE_REQ 0x70000262 -#define REG_G_ACTIVE_PREV_CFG 0x70000266 -#define REG_G_PREV_CFG_CHG 0x70000268 -#define REG_G_PREV_OPEN_AFTER_CH 0x7000026a - -/* Preview context register sets. n = 0...4. */ -#define PREG(n, x) ((n) * 0x30 + (x)) -#define REG_P_OUT_WIDTH(n) PREG(n, 0x700002a6) -#define REG_P_OUT_HEIGHT(n) PREG(n, 0x700002a8) -#define REG_P_FMT(n) PREG(n, 0x700002aa) -#define REG_P_PVI_MASK(n) PREG(n, 0x700002b4) -#define REG_P_FR_TIME_TYPE(n) PREG(n, 0x700002be) -#define FR_TIME_DYNAMIC 0 -#define FR_TIME_FIXED 1 -#define FR_TIME_FIXED_ACCURATE 2 -#define REG_P_FR_TIME_Q_TYPE(n) PREG(n, 0x700002c0) -#define FR_TIME_Q_DYNAMIC 0 -#define FR_TIME_Q_BEST_FRRATE 1 -#define FR_TIME_Q_BEST_QUALITY 2 - -/* Frame period in 0.1 ms units */ -#define REG_P_MAX_FR_TIME(n) PREG(n, 0x700002c2) -#define REG_P_MIN_FR_TIME(n) PREG(n, 0x700002c4) -#define US_TO_FR_TIME(__t) ((__t) / 100) -#define REG_P_PREV_MIRROR(n) PREG(n, 0x700002d0) -#define REG_P_CAP_MIRROR(n) PREG(n, 0x700002d2) - -#define REG_G_PREVZOOM_IN_WIDTH 0x70000494 -#define REG_G_PREVZOOM_IN_HEIGHT 0x70000496 -#define REG_G_PREVZOOM_IN_XOFFS 0x70000498 -#define REG_G_PREVZOOM_IN_YOFFS 0x7000049a -#define REG_G_CAPZOOM_IN_WIDTH 0x7000049c -#define REG_G_CAPZOOM_IN_HEIGHT 0x7000049e -#define REG_G_CAPZOOM_IN_XOFFS 0x700004a0 -#define REG_G_CAPZOOM_IN_YOFFS 0x700004a2 - -/* n = 0...4 */ -#define REG_USER_SHARPNESS(n) (0x70000a28 + (n) * 0xb6) - -/* Reduce sharpness range for user space API */ -#define SHARPNESS_DIV 8208 -#define TOK_TERM 0xffffffff - -/* - * FIXME: This is copied from s5k6aa, because of no information - * in the S5K4ECGX datasheet. - * H/W register Interface (0xd0000000 - 0xd0000fff) - */ -#define AHB_MSB_ADDR_PTR 0xfcfc -#define GEN_REG_OFFSH 0xd000 -#define REG_CMDWR_ADDRH 0x0028 -#define REG_CMDWR_ADDRL 0x002a -#define REG_CMDRD_ADDRH 0x002c -#define REG_CMDRD_ADDRL 0x002e -#define REG_CMDBUF0_ADDR 0x0f12 - -struct s5k4ecgx_frmsize { - struct v4l2_frmsize_discrete size; - /* Fixed sensor matrix crop rectangle */ - struct v4l2_rect input_window; -}; - -struct regval_list { - u32 addr; - u16 val; -}; - -/* - * TODO: currently only preview is supported and snapshot (capture) - * is not implemented yet - */ -static const struct s5k4ecgx_frmsize s5k4ecgx_prev_sizes[] = { - { - .size = { 176, 144 }, - .input_window = { 0x00, 0x00, 0x928, 0x780 }, - }, { - .size = { 352, 288 }, - .input_window = { 0x00, 0x00, 0x928, 0x780 }, - }, { - .size = { 640, 480 }, - .input_window = { 0x00, 0x00, 0xa00, 0x780 }, - }, { - .size = { 720, 480 }, - .input_window = { 0x00, 0x00, 0xa00, 0x6a8 }, - } -}; - -#define S5K4ECGX_NUM_PREV ARRAY_SIZE(s5k4ecgx_prev_sizes) - -struct s5k4ecgx_pixfmt { - u32 code; - u32 colorspace; - /* REG_TC_PCFG_Format register value */ - u16 reg_p_format; -}; - -/* By default value, output from sensor will be YUV422 0-255 */ -static const struct s5k4ecgx_pixfmt s5k4ecgx_formats[] = { - { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 5 }, -}; - -static const char * const s5k4ecgx_supply_names[] = { - /* - * Usually 2.8V is used for analog power (vdda) - * and digital IO (vddio, vdddcore) - */ - "vdda", - "vddio", - "vddcore", - "vddreg", /* The internal s5k4ecgx regulator's supply (1.8V) */ -}; - -#define S5K4ECGX_NUM_SUPPLIES ARRAY_SIZE(s5k4ecgx_supply_names) - -enum s5k4ecgx_gpio_id { - STBY, - RSET, - GPIO_NUM, -}; - -struct s5k4ecgx { - struct v4l2_subdev sd; - struct media_pad pad; - struct v4l2_ctrl_handler handler; - - struct s5k4ecgx_platform_data *pdata; - const struct s5k4ecgx_pixfmt *curr_pixfmt; - const struct s5k4ecgx_frmsize *curr_frmsize; - struct mutex lock; - u8 streaming; - u8 set_params; - - struct regulator_bulk_data supplies[S5K4ECGX_NUM_SUPPLIES]; - struct s5k4ecgx_gpio gpio[GPIO_NUM]; -}; - -static inline struct s5k4ecgx *to_s5k4ecgx(struct v4l2_subdev *sd) -{ - return container_of(sd, struct s5k4ecgx, sd); -} - -static int s5k4ecgx_i2c_read(struct i2c_client *client, u16 addr, u16 *val) -{ - u8 wbuf[2] = { addr >> 8, addr & 0xff }; - struct i2c_msg msg[2]; - u8 rbuf[2]; - int ret; - - msg[0].addr = client->addr; - msg[0].flags = 0; - msg[0].len = 2; - msg[0].buf = wbuf; - - msg[1].addr = client->addr; - msg[1].flags = I2C_M_RD; - msg[1].len = 2; - msg[1].buf = rbuf; - - ret = i2c_transfer(client->adapter, msg, 2); - *val = be16_to_cpu(*((__be16 *)rbuf)); - - v4l2_dbg(4, debug, client, "i2c_read: 0x%04X : 0x%04x\n", addr, *val); - - return ret == 2 ? 0 : ret; -} - -static int s5k4ecgx_i2c_write(struct i2c_client *client, u16 addr, u16 val) -{ - u8 buf[4] = { addr >> 8, addr & 0xff, val >> 8, val & 0xff }; - - int ret = i2c_master_send(client, buf, 4); - v4l2_dbg(4, debug, client, "i2c_write: 0x%04x : 0x%04x\n", addr, val); - - return ret == 4 ? 0 : ret; -} - -static int s5k4ecgx_write(struct i2c_client *client, u32 addr, u16 val) -{ - u16 high = addr >> 16, low = addr & 0xffff; - int ret; - - v4l2_dbg(3, debug, client, "write: 0x%08x : 0x%04x\n", addr, val); - - ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRH, high); - if (!ret) - ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRL, low); - if (!ret) - ret = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); - - return ret; -} - -static int s5k4ecgx_read(struct i2c_client *client, u32 addr, u16 *val) -{ - u16 high = addr >> 16, low = addr & 0xffff; - int ret; - - ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRH, high); - if (!ret) - ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRL, low); - if (!ret) - ret = s5k4ecgx_i2c_read(client, REG_CMDBUF0_ADDR, val); - - return ret; -} - -static int s5k4ecgx_read_fw_ver(struct v4l2_subdev *sd) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - u16 hw_rev, fw_ver = 0; - int ret; - - ret = s5k4ecgx_read(client, REG_FW_VERSION, &fw_ver); - if (ret < 0 || fw_ver != S5K4ECGX_FW_VERSION) { - v4l2_err(sd, "FW version check failed!\n"); - return -ENODEV; - } - - ret = s5k4ecgx_read(client, REG_FW_REVISION, &hw_rev); - if (ret < 0) - return ret; - - v4l2_info(sd, "chip found FW ver: 0x%x, HW rev: 0x%x\n", - fw_ver, hw_rev); - return 0; -} - -static int s5k4ecgx_set_ahb_address(struct v4l2_subdev *sd) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - - /* Set APB peripherals start address */ - ret = s5k4ecgx_i2c_write(client, AHB_MSB_ADDR_PTR, GEN_REG_OFFSH); - if (ret < 0) - return ret; - /* - * FIXME: This is copied from s5k6aa, because of no information - * in s5k4ecgx's datasheet. - * sw_reset is activated to put device into idle status - */ - ret = s5k4ecgx_i2c_write(client, 0x0010, 0x0001); - if (ret < 0) - return ret; - - ret = s5k4ecgx_i2c_write(client, 0x1030, 0x0000); - if (ret < 0) - return ret; - /* Halt ARM CPU */ - return s5k4ecgx_i2c_write(client, 0x0014, 0x0001); -} - -#define FW_CRC_SIZE 4 -/* Register address, value are 4, 2 bytes */ -#define FW_RECORD_SIZE 6 -/* - * The firmware has following format: - * < total number of records (4 bytes + 2 bytes padding) N >, - * < record 0 >, ..., < record N - 1 >, < CRC32-CCITT (4-bytes) >, - * where "record" is a 4-byte register address followed by 2-byte - * register value (little endian). - * The firmware generator can be found in following git repository: - * git://git.linaro.org/people/sangwook/fimc-v4l2-app.git - */ -static int s5k4ecgx_load_firmware(struct v4l2_subdev *sd) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - const struct firmware *fw; - const u8 *ptr; - int err, i, regs_num; - u32 addr, crc, crc_file, addr_inc = 0; - u16 val; - - err = request_firmware(&fw, S5K4ECGX_FIRMWARE, sd->v4l2_dev->dev); - if (err) { - v4l2_err(sd, "Failed to read firmware %s\n", S5K4ECGX_FIRMWARE); - return err; - } - regs_num = get_unaligned_le32(fw->data); - - v4l2_dbg(3, debug, sd, "FW: %s size %zu register sets %d\n", - S5K4ECGX_FIRMWARE, fw->size, regs_num); - - regs_num++; /* Add header */ - if (fw->size != regs_num * FW_RECORD_SIZE + FW_CRC_SIZE) { - err = -EINVAL; - goto fw_out; - } - crc_file = get_unaligned_le32(fw->data + regs_num * FW_RECORD_SIZE); - crc = crc32_le(~0, fw->data, regs_num * FW_RECORD_SIZE); - if (crc != crc_file) { - v4l2_err(sd, "FW: invalid crc (%#x:%#x)\n", crc, crc_file); - err = -EINVAL; - goto fw_out; - } - ptr = fw->data + FW_RECORD_SIZE; - for (i = 1; i < regs_num; i++) { - addr = get_unaligned_le32(ptr); - ptr += sizeof(u32); - val = get_unaligned_le16(ptr); - ptr += sizeof(u16); - if (addr - addr_inc != 2) - err = s5k4ecgx_write(client, addr, val); - else - err = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); - if (err) - break; - addr_inc = addr; - } -fw_out: - release_firmware(fw); - return err; -} - -/* Set preview and capture input window */ -static int s5k4ecgx_set_input_window(struct i2c_client *c, - const struct v4l2_rect *r) -{ - int ret; - - ret = s5k4ecgx_write(c, REG_G_PREV_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREV_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREV_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREV_IN_YOFFS, r->top); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_YOFFS, r->top); - - return ret; -} - -/* Set preview and capture zoom input window */ -static int s5k4ecgx_set_zoom_window(struct i2c_client *c, - const struct v4l2_rect *r) -{ - int ret; - - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_YOFFS, r->top); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_YOFFS, r->top); - - return ret; -} - -static int s5k4ecgx_set_output_framefmt(struct s5k4ecgx *priv) -{ - struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); - int ret; - - ret = s5k4ecgx_write(client, REG_P_OUT_WIDTH(0), - priv->curr_frmsize->size.width); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_OUT_HEIGHT(0), - priv->curr_frmsize->size.height); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_FMT(0), - priv->curr_pixfmt->reg_p_format); - return ret; -} - -static int s5k4ecgx_init_sensor(struct v4l2_subdev *sd) -{ - int ret; - - ret = s5k4ecgx_set_ahb_address(sd); - - /* The delay is from manufacturer's settings */ - msleep(100); - - if (!ret) - ret = s5k4ecgx_load_firmware(sd); - if (ret) - v4l2_err(sd, "Failed to write initial settings\n"); - - return ret; -} - -static int s5k4ecgx_gpio_set_value(struct s5k4ecgx *priv, int id, u32 val) -{ - if (!gpio_is_valid(priv->gpio[id].gpio)) - return 0; - gpio_set_value(priv->gpio[id].gpio, val); - - return 1; -} - -static int __s5k4ecgx_power_on(struct s5k4ecgx *priv) -{ - int ret; - - ret = regulator_bulk_enable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); - if (ret) - return ret; - usleep_range(30, 50); - - /* The polarity of STBY is controlled by TSP */ - if (s5k4ecgx_gpio_set_value(priv, STBY, priv->gpio[STBY].level)) - usleep_range(30, 50); - - if (s5k4ecgx_gpio_set_value(priv, RSET, priv->gpio[RSET].level)) - usleep_range(30, 50); - - return 0; -} - -static int __s5k4ecgx_power_off(struct s5k4ecgx *priv) -{ - if (s5k4ecgx_gpio_set_value(priv, RSET, !priv->gpio[RSET].level)) - usleep_range(30, 50); - - if (s5k4ecgx_gpio_set_value(priv, STBY, !priv->gpio[STBY].level)) - usleep_range(30, 50); - - priv->streaming = 0; - - return regulator_bulk_disable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); -} - -/* Find nearest matching image pixel size. */ -static int s5k4ecgx_try_frame_size(struct v4l2_mbus_framefmt *mf, - const struct s5k4ecgx_frmsize **size) -{ - unsigned int min_err = ~0; - int i = ARRAY_SIZE(s5k4ecgx_prev_sizes); - const struct s5k4ecgx_frmsize *fsize = &s5k4ecgx_prev_sizes[0], - *match = NULL; - - while (i--) { - int err = abs(fsize->size.width - mf->width) - + abs(fsize->size.height - mf->height); - if (err < min_err) { - min_err = err; - match = fsize; - } - fsize++; - } - if (match) { - mf->width = match->size.width; - mf->height = match->size.height; - if (size) - *size = match; - return 0; - } - - return -EINVAL; -} - -static int s5k4ecgx_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_mbus_code_enum *code) -{ - if (code->index >= ARRAY_SIZE(s5k4ecgx_formats)) - return -EINVAL; - code->code = s5k4ecgx_formats[code->index].code; - - return 0; -} - -static int s5k4ecgx_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - struct v4l2_mbus_framefmt *mf; - - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - if (sd_state) { - mf = v4l2_subdev_get_try_format(sd, sd_state, 0); - fmt->format = *mf; - } - return 0; - } - - mf = &fmt->format; - - mutex_lock(&priv->lock); - mf->width = priv->curr_frmsize->size.width; - mf->height = priv->curr_frmsize->size.height; - mf->code = priv->curr_pixfmt->code; - mf->colorspace = priv->curr_pixfmt->colorspace; - mf->field = V4L2_FIELD_NONE; - mutex_unlock(&priv->lock); - - return 0; -} - -static const struct s5k4ecgx_pixfmt *s5k4ecgx_try_fmt(struct v4l2_subdev *sd, - struct v4l2_mbus_framefmt *mf) -{ - int i = ARRAY_SIZE(s5k4ecgx_formats); - - while (--i) - if (mf->code == s5k4ecgx_formats[i].code) - break; - mf->code = s5k4ecgx_formats[i].code; - - return &s5k4ecgx_formats[i]; -} - -static int s5k4ecgx_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - const struct s5k4ecgx_frmsize *fsize = NULL; - const struct s5k4ecgx_pixfmt *pf; - struct v4l2_mbus_framefmt *mf; - int ret = 0; - - pf = s5k4ecgx_try_fmt(sd, &fmt->format); - s5k4ecgx_try_frame_size(&fmt->format, &fsize); - fmt->format.colorspace = V4L2_COLORSPACE_JPEG; - fmt->format.field = V4L2_FIELD_NONE; - - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - if (sd_state) { - mf = v4l2_subdev_get_try_format(sd, sd_state, 0); - *mf = fmt->format; - } - return 0; - } - - mutex_lock(&priv->lock); - if (!priv->streaming) { - priv->curr_frmsize = fsize; - priv->curr_pixfmt = pf; - priv->set_params = 1; - } else { - ret = -EBUSY; - } - mutex_unlock(&priv->lock); - - return ret; -} - -static const struct v4l2_subdev_pad_ops s5k4ecgx_pad_ops = { - .enum_mbus_code = s5k4ecgx_enum_mbus_code, - .get_fmt = s5k4ecgx_get_fmt, - .set_fmt = s5k4ecgx_set_fmt, -}; - -/* - * V4L2 subdev controls - */ -static int s5k4ecgx_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct v4l2_subdev *sd = &container_of(ctrl->handler, struct s5k4ecgx, - handler)->sd; - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - unsigned int i; - int err = 0; - - v4l2_dbg(1, debug, sd, "ctrl: 0x%x, value: %d\n", ctrl->id, ctrl->val); - - mutex_lock(&priv->lock); - switch (ctrl->id) { - case V4L2_CID_CONTRAST: - err = s5k4ecgx_write(client, REG_USER_CONTRAST, ctrl->val); - break; - - case V4L2_CID_SATURATION: - err = s5k4ecgx_write(client, REG_USER_SATURATION, ctrl->val); - break; - - case V4L2_CID_SHARPNESS: - /* TODO: Revisit, is this setting for all presets ? */ - for (i = 0; i < 4 && !err; i++) - err = s5k4ecgx_write(client, REG_USER_SHARPNESS(i), - ctrl->val * SHARPNESS_DIV); - break; - - case V4L2_CID_BRIGHTNESS: - err = s5k4ecgx_write(client, REG_USER_BRIGHTNESS, ctrl->val); - break; - } - mutex_unlock(&priv->lock); - if (err < 0) - v4l2_err(sd, "Failed to write s_ctrl err %d\n", err); - - return err; -} - -static const struct v4l2_ctrl_ops s5k4ecgx_ctrl_ops = { - .s_ctrl = s5k4ecgx_s_ctrl, -}; - -/* - * Reading s5k4ecgx version information - */ -static int s5k4ecgx_registered(struct v4l2_subdev *sd) -{ - int ret; - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - - mutex_lock(&priv->lock); - ret = __s5k4ecgx_power_on(priv); - if (!ret) { - ret = s5k4ecgx_read_fw_ver(sd); - __s5k4ecgx_power_off(priv); - } - mutex_unlock(&priv->lock); - - return ret; -} - -/* - * V4L2 subdev internal operations - */ -static int s5k4ecgx_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct v4l2_mbus_framefmt *mf = v4l2_subdev_get_try_format(sd, - fh->state, - 0); - - mf->width = s5k4ecgx_prev_sizes[0].size.width; - mf->height = s5k4ecgx_prev_sizes[0].size.height; - mf->code = s5k4ecgx_formats[0].code; - mf->colorspace = V4L2_COLORSPACE_JPEG; - mf->field = V4L2_FIELD_NONE; - - return 0; -} - -static const struct v4l2_subdev_internal_ops s5k4ecgx_subdev_internal_ops = { - .registered = s5k4ecgx_registered, - .open = s5k4ecgx_open, -}; - -static int s5k4ecgx_s_power(struct v4l2_subdev *sd, int on) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - int ret; - - v4l2_dbg(1, debug, sd, "Switching %s\n", on ? "on" : "off"); - - if (on) { - ret = __s5k4ecgx_power_on(priv); - if (ret < 0) - return ret; - /* Time to stabilize sensor */ - msleep(100); - ret = s5k4ecgx_init_sensor(sd); - if (ret < 0) - __s5k4ecgx_power_off(priv); - else - priv->set_params = 1; - } else { - ret = __s5k4ecgx_power_off(priv); - } - - return ret; -} - -static int s5k4ecgx_log_status(struct v4l2_subdev *sd) -{ - v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); - - return 0; -} - -static const struct v4l2_subdev_core_ops s5k4ecgx_core_ops = { - .s_power = s5k4ecgx_s_power, - .log_status = s5k4ecgx_log_status, -}; - -static int __s5k4ecgx_s_params(struct s5k4ecgx *priv) -{ - struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); - const struct v4l2_rect *crop_rect = &priv->curr_frmsize->input_window; - int ret; - - ret = s5k4ecgx_set_input_window(client, crop_rect); - if (!ret) - ret = s5k4ecgx_set_zoom_window(client, crop_rect); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_INPUTS_CHANGE_REQ, 1); - if (!ret) - ret = s5k4ecgx_write(client, 0x70000a1e, 0x28); - if (!ret) - ret = s5k4ecgx_write(client, 0x70000ad4, 0x3c); - if (!ret) - ret = s5k4ecgx_set_output_framefmt(priv); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_PVI_MASK(0), 0x52); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_FR_TIME_TYPE(0), - FR_TIME_DYNAMIC); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_FR_TIME_Q_TYPE(0), - FR_TIME_Q_BEST_FRRATE); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_MIN_FR_TIME(0), - US_TO_FR_TIME(33300)); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_MAX_FR_TIME(0), - US_TO_FR_TIME(66600)); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_PREV_MIRROR(0), 0); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_CAP_MIRROR(0), 0); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_ACTIVE_PREV_CFG, 0); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_PREV_OPEN_AFTER_CH, 1); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_NEW_CFG_SYNC, 1); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_PREV_CFG_CHG, 1); - - return ret; -} - -static int __s5k4ecgx_s_stream(struct s5k4ecgx *priv, int on) -{ - struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); - int ret; - - if (on && priv->set_params) { - ret = __s5k4ecgx_s_params(priv); - if (ret < 0) - return ret; - priv->set_params = 0; - } - /* - * This enables/disables preview stream only. Capture requests - * are not supported yet. - */ - ret = s5k4ecgx_write(client, REG_G_ENABLE_PREV, on); - if (ret < 0) - return ret; - return s5k4ecgx_write(client, REG_G_ENABLE_PREV_CHG, 1); -} - -static int s5k4ecgx_s_stream(struct v4l2_subdev *sd, int on) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - int ret = 0; - - v4l2_dbg(1, debug, sd, "Turn streaming %s\n", on ? "on" : "off"); - - mutex_lock(&priv->lock); - - if (priv->streaming == !on) { - ret = __s5k4ecgx_s_stream(priv, on); - if (!ret) - priv->streaming = on & 1; - } - - mutex_unlock(&priv->lock); - return ret; -} - -static const struct v4l2_subdev_video_ops s5k4ecgx_video_ops = { - .s_stream = s5k4ecgx_s_stream, -}; - -static const struct v4l2_subdev_ops s5k4ecgx_ops = { - .core = &s5k4ecgx_core_ops, - .pad = &s5k4ecgx_pad_ops, - .video = &s5k4ecgx_video_ops, -}; - -/* - * GPIO setup - */ -static int s5k4ecgx_config_gpio(int nr, int val, const char *name) -{ - unsigned long flags = val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; - int ret; - - if (!gpio_is_valid(nr)) - return 0; - ret = gpio_request_one(nr, flags, name); - if (!ret) - gpio_export(nr, 0); - - return ret; -} - -static void s5k4ecgx_free_gpios(struct s5k4ecgx *priv) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(priv->gpio); i++) { - if (!gpio_is_valid(priv->gpio[i].gpio)) - continue; - gpio_free(priv->gpio[i].gpio); - priv->gpio[i].gpio = -EINVAL; - } -} - -static int s5k4ecgx_config_gpios(struct s5k4ecgx *priv, - const struct s5k4ecgx_platform_data *pdata) -{ - const struct s5k4ecgx_gpio *gpio = &pdata->gpio_stby; - int ret; - - priv->gpio[STBY].gpio = -EINVAL; - priv->gpio[RSET].gpio = -EINVAL; - - ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_STBY"); - - if (ret) { - s5k4ecgx_free_gpios(priv); - return ret; - } - priv->gpio[STBY] = *gpio; - if (gpio_is_valid(gpio->gpio)) - gpio_set_value(gpio->gpio, 0); - - gpio = &pdata->gpio_reset; - - ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_RST"); - if (ret) { - s5k4ecgx_free_gpios(priv); - return ret; - } - priv->gpio[RSET] = *gpio; - if (gpio_is_valid(gpio->gpio)) - gpio_set_value(gpio->gpio, 0); - - return 0; -} - -static int s5k4ecgx_init_v4l2_ctrls(struct s5k4ecgx *priv) -{ - const struct v4l2_ctrl_ops *ops = &s5k4ecgx_ctrl_ops; - struct v4l2_ctrl_handler *hdl = &priv->handler; - int ret; - - ret = v4l2_ctrl_handler_init(hdl, 4); - if (ret) - return ret; - - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0); - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); - - /* Sharpness default is 24612, and then (24612/SHARPNESS_DIV) = 2 */ - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -32704/SHARPNESS_DIV, - 24612/SHARPNESS_DIV, 1, 2); - if (hdl->error) { - ret = hdl->error; - v4l2_ctrl_handler_free(hdl); - return ret; - } - priv->sd.ctrl_handler = hdl; - - return 0; -}; - -static int s5k4ecgx_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; - struct v4l2_subdev *sd; - struct s5k4ecgx *priv; - int ret, i; - - if (pdata == NULL) { - dev_err(&client->dev, "platform data is missing!\n"); - return -EINVAL; - } - - priv = devm_kzalloc(&client->dev, sizeof(struct s5k4ecgx), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - mutex_init(&priv->lock); - priv->streaming = 0; - - sd = &priv->sd; - /* Registering subdev */ - v4l2_i2c_subdev_init(sd, client, &s5k4ecgx_ops); - /* Static name; NEVER use in new drivers! */ - strscpy(sd->name, S5K4ECGX_DRIVER_NAME, sizeof(sd->name)); - - sd->internal_ops = &s5k4ecgx_subdev_internal_ops; - /* Support v4l2 sub-device user space API */ - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - - priv->pad.flags = MEDIA_PAD_FL_SOURCE; - sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; - ret = media_entity_pads_init(&sd->entity, 1, &priv->pad); - if (ret) - return ret; - - ret = s5k4ecgx_config_gpios(priv, pdata); - if (ret) { - dev_err(&client->dev, "Failed to set gpios\n"); - goto out_err1; - } - for (i = 0; i < S5K4ECGX_NUM_SUPPLIES; i++) - priv->supplies[i].supply = s5k4ecgx_supply_names[i]; - - ret = devm_regulator_bulk_get(&client->dev, S5K4ECGX_NUM_SUPPLIES, - priv->supplies); - if (ret) { - dev_err(&client->dev, "Failed to get regulators\n"); - goto out_err2; - } - ret = s5k4ecgx_init_v4l2_ctrls(priv); - if (ret) - goto out_err2; - - priv->curr_pixfmt = &s5k4ecgx_formats[0]; - priv->curr_frmsize = &s5k4ecgx_prev_sizes[0]; - - return 0; - -out_err2: - s5k4ecgx_free_gpios(priv); -out_err1: - media_entity_cleanup(&priv->sd.entity); - - return ret; -} - -static void s5k4ecgx_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - - mutex_destroy(&priv->lock); - s5k4ecgx_free_gpios(priv); - v4l2_device_unregister_subdev(sd); - v4l2_ctrl_handler_free(&priv->handler); - media_entity_cleanup(&sd->entity); -} - -static const struct i2c_device_id s5k4ecgx_id[] = { - { S5K4ECGX_DRIVER_NAME, 0 }, - {} -}; -MODULE_DEVICE_TABLE(i2c, s5k4ecgx_id); - -static struct i2c_driver v4l2_i2c_driver = { - .driver = { - .name = S5K4ECGX_DRIVER_NAME, - }, - .probe = s5k4ecgx_probe, - .remove = s5k4ecgx_remove, - .id_table = s5k4ecgx_id, -}; - -module_i2c_driver(v4l2_i2c_driver); - -MODULE_DESCRIPTION("Samsung S5K4ECGX 5MP SOC camera"); -MODULE_AUTHOR("Sangwook Lee <sangwook.lee@linaro.org>"); -MODULE_AUTHOR("Seok-Young Jang <quartz.jang@samsung.com>"); -MODULE_LICENSE("GPL"); -MODULE_FIRMWARE(S5K4ECGX_FIRMWARE); diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c index 5c2253ab3b6f..960fbf6428ea 100644 --- a/drivers/media/i2c/s5k5baf.c +++ b/drivers/media/i2c/s5k5baf.c @@ -13,11 +13,10 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/firmware.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/media.h> #include <linux/module.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -228,11 +227,6 @@ static const char * const s5k5baf_supply_names[] = { }; #define S5K5BAF_NUM_SUPPLIES ARRAY_SIZE(s5k5baf_supply_names) -struct s5k5baf_gpio { - int gpio; - int level; -}; - enum s5k5baf_gpio_id { STBY, RSET, @@ -284,7 +278,7 @@ struct s5k5baf_fw { }; struct s5k5baf { - struct s5k5baf_gpio gpios[NUM_GPIOS]; + struct gpio_desc *gpios[NUM_GPIOS]; enum v4l2_mbus_type bus_type; u8 nlanes; struct regulator_bulk_data supplies[S5K5BAF_NUM_SUPPLIES]; @@ -936,16 +930,12 @@ static void s5k5baf_hw_set_test_pattern(struct s5k5baf *state, int id) static void s5k5baf_gpio_assert(struct s5k5baf *state, int id) { - struct s5k5baf_gpio *gpio = &state->gpios[id]; - - gpio_set_value(gpio->gpio, gpio->level); + gpiod_set_value_cansleep(state->gpios[id], 1); } static void s5k5baf_gpio_deassert(struct s5k5baf *state, int id) { - struct s5k5baf_gpio *gpio = &state->gpios[id]; - - gpio_set_value(gpio->gpio, !gpio->level); + gpiod_set_value_cansleep(state->gpios[id], 0); } static int s5k5baf_power_on(struct s5k5baf *state) @@ -1799,44 +1789,30 @@ static const struct v4l2_subdev_ops s5k5baf_subdev_ops = { static int s5k5baf_configure_gpios(struct s5k5baf *state) { - static const char * const name[] = { "S5K5BAF_STBY", "S5K5BAF_RST" }; + static const char * const name[] = { "stbyn", "rstn" }; + static const char * const label[] = { "S5K5BAF_STBY", "S5K5BAF_RST" }; struct i2c_client *c = v4l2_get_subdevdata(&state->sd); - struct s5k5baf_gpio *g = state->gpios; + struct gpio_desc *gpio; int ret, i; for (i = 0; i < NUM_GPIOS; ++i) { - int flags = GPIOF_DIR_OUT; - if (g[i].level) - flags |= GPIOF_INIT_HIGH; - ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags, name[i]); - if (ret < 0) { - v4l2_err(c, "failed to request gpio %s\n", name[i]); + gpio = devm_gpiod_get(&c->dev, name[i], GPIOD_OUT_HIGH); + ret = PTR_ERR_OR_ZERO(gpio); + if (ret) { + v4l2_err(c, "failed to request gpio %s: %d\n", + name[i], ret); return ret; } - } - return 0; -} - -static int s5k5baf_parse_gpios(struct s5k5baf_gpio *gpios, struct device *dev) -{ - static const char * const names[] = { - "stbyn-gpios", - "rstn-gpios", - }; - struct device_node *node = dev->of_node; - enum of_gpio_flags flags; - int ret, i; - for (i = 0; i < NUM_GPIOS; ++i) { - ret = of_get_named_gpio_flags(node, names[i], 0, &flags); - if (ret < 0) { - dev_err(dev, "no %s GPIO pin provided\n", names[i]); + ret = gpiod_set_consumer_name(gpio, label[i]); + if (ret) { + v4l2_err(c, "failed to set up name for gpio %s: %d\n", + name[i], ret); return ret; } - gpios[i].gpio = ret; - gpios[i].level = !(flags & OF_GPIO_ACTIVE_LOW); - } + state->gpios[i] = gpio; + } return 0; } @@ -1860,10 +1836,6 @@ static int s5k5baf_parse_device_node(struct s5k5baf *state, struct device *dev) state->mclk_frequency); } - ret = s5k5baf_parse_gpios(state->gpios, dev); - if (ret < 0) - return ret; - node_ep = of_graph_get_next_endpoint(node, NULL); if (!node_ep) { dev_err(dev, "no endpoint defined at node %pOF\n", node); diff --git a/drivers/media/i2c/s5k6a3.c b/drivers/media/i2c/s5k6a3.c index a4efd6d10b43..ef6673b10580 100644 --- a/drivers/media/i2c/s5k6a3.c +++ b/drivers/media/i2c/s5k6a3.c @@ -9,12 +9,12 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/err.h> #include <linux/errno.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_gpio.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -59,7 +59,7 @@ struct s5k6a3 { struct v4l2_subdev subdev; struct media_pad pad; struct regulator_bulk_data supplies[S5K6A3_NUM_SUPPLIES]; - int gpio_reset; + struct gpio_desc *gpio_reset; struct mutex lock; struct v4l2_mbus_framefmt format; struct clk *clock; @@ -216,11 +216,11 @@ static int __s5k6a3_power_on(struct s5k6a3 *sensor) goto error_clk; } - gpio_set_value(sensor->gpio_reset, 1); + gpiod_set_value_cansleep(sensor->gpio_reset, 0); usleep_range(600, 800); - gpio_set_value(sensor->gpio_reset, 0); + gpiod_set_value_cansleep(sensor->gpio_reset, 1); usleep_range(600, 800); - gpio_set_value(sensor->gpio_reset, 1); + gpiod_set_value_cansleep(sensor->gpio_reset, 0); /* Delay needed for the sensor initialization */ msleep(20); @@ -240,7 +240,7 @@ static int __s5k6a3_power_off(struct s5k6a3 *sensor) { int i; - gpio_set_value(sensor->gpio_reset, 0); + gpiod_set_value_cansleep(sensor->gpio_reset, 1); for (i = S5K6A3_NUM_SUPPLIES - 1; i >= 0; i--) regulator_disable(sensor->supplies[i].consumer); @@ -285,32 +285,24 @@ static int s5k6a3_probe(struct i2c_client *client) struct device *dev = &client->dev; struct s5k6a3 *sensor; struct v4l2_subdev *sd; - int gpio, i, ret; + int i, ret; sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); if (!sensor) return -ENOMEM; mutex_init(&sensor->lock); - sensor->gpio_reset = -EINVAL; - sensor->clock = ERR_PTR(-EINVAL); sensor->dev = dev; sensor->clock = devm_clk_get(sensor->dev, S5K6A3_CLK_NAME); if (IS_ERR(sensor->clock)) return PTR_ERR(sensor->clock); - gpio = of_get_gpio_flags(dev->of_node, 0, NULL); - if (!gpio_is_valid(gpio)) - return gpio; - - ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_LOW, - S5K6A3_DRV_NAME); - if (ret < 0) + sensor->gpio_reset = devm_gpiod_get(dev, NULL, GPIOD_OUT_HIGH); + ret = PTR_ERR_OR_ZERO(sensor->gpio_reset); + if (ret) return ret; - sensor->gpio_reset = gpio; - if (of_property_read_u32(dev->of_node, "clock-frequency", &sensor->clock_frequency)) { sensor->clock_frequency = S5K6A3_DEFAULT_CLK_FREQ; diff --git a/drivers/media/i2c/s5k6aa.c b/drivers/media/i2c/s5k6aa.c index 059211788a65..5996153371fc 100644 --- a/drivers/media/i2c/s5k6aa.c +++ b/drivers/media/i2c/s5k6aa.c @@ -1544,8 +1544,7 @@ static int s5k6aa_configure_gpios(struct s5k6aa *s5k6aa, return 0; } -static int s5k6aa_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int s5k6aa_probe(struct i2c_client *client) { const struct s5k6aa_platform_data *pdata = client->dev.platform_data; struct v4l2_subdev *sd; @@ -1641,7 +1640,7 @@ static struct i2c_driver s5k6aa_i2c_driver = { .driver = { .name = DRIVER_NAME }, - .probe = s5k6aa_probe, + .probe_new = s5k6aa_probe, .remove = s5k6aa_remove, .id_table = s5k6aa_id, }; diff --git a/drivers/media/i2c/saa6588.c b/drivers/media/i2c/saa6588.c index d6a51beabd02..8752f7cff611 100644 --- a/drivers/media/i2c/saa6588.c +++ b/drivers/media/i2c/saa6588.c @@ -448,8 +448,7 @@ static const struct v4l2_subdev_ops saa6588_ops = { /* ---------------------------------------------------------------------- */ -static int saa6588_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa6588_probe(struct i2c_client *client) { struct saa6588 *s; struct v4l2_subdev *sd; @@ -506,7 +505,7 @@ static struct i2c_driver saa6588_driver = { .driver = { .name = "saa6588", }, - .probe = saa6588_probe, + .probe_new = saa6588_probe, .remove = saa6588_remove, .id_table = saa6588_id, }; diff --git a/drivers/media/i2c/saa6752hs.c b/drivers/media/i2c/saa6752hs.c index 5928cc6f4595..892d64fe6e81 100644 --- a/drivers/media/i2c/saa6752hs.c +++ b/drivers/media/i2c/saa6752hs.c @@ -659,8 +659,7 @@ static const struct v4l2_subdev_ops saa6752hs_ops = { .pad = &saa6752hs_pad_ops, }; -static int saa6752hs_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa6752hs_probe(struct i2c_client *client) { struct saa6752hs_state *h; struct v4l2_subdev *sd; @@ -782,7 +781,7 @@ static struct i2c_driver saa6752hs_driver = { .driver = { .name = "saa6752hs", }, - .probe = saa6752hs_probe, + .probe_new = saa6752hs_probe, .remove = saa6752hs_remove, .id_table = saa6752hs_id, }; diff --git a/drivers/media/i2c/saa7110.c b/drivers/media/i2c/saa7110.c index 5067525d8b11..b58e71517376 100644 --- a/drivers/media/i2c/saa7110.c +++ b/drivers/media/i2c/saa7110.c @@ -358,8 +358,7 @@ static const struct v4l2_subdev_ops saa7110_ops = { /* ----------------------------------------------------------------------- */ -static int saa7110_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7110_probe(struct i2c_client *client) { struct saa7110 *decoder; struct v4l2_subdev *sd; @@ -449,7 +448,7 @@ static struct i2c_driver saa7110_driver = { .driver = { .name = "saa7110", }, - .probe = saa7110_probe, + .probe_new = saa7110_probe, .remove = saa7110_remove, .id_table = saa7110_id, }; diff --git a/drivers/media/i2c/saa717x.c b/drivers/media/i2c/saa717x.c index 4f3d1b432a4e..df01059076fa 100644 --- a/drivers/media/i2c/saa717x.c +++ b/drivers/media/i2c/saa717x.c @@ -1228,8 +1228,7 @@ static const struct v4l2_subdev_ops saa717x_ops = { /* i2c implementation */ /* ----------------------------------------------------------------------- */ -static int saa717x_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int saa717x_probe(struct i2c_client *client) { struct saa717x_state *decoder; struct v4l2_ctrl_handler *hdl; @@ -1344,7 +1343,7 @@ static struct i2c_driver saa717x_driver = { .driver = { .name = "saa717x", }, - .probe = saa717x_probe, + .probe_new = saa717x_probe, .remove = saa717x_remove, .id_table = saa717x_id, }; diff --git a/drivers/media/i2c/saa7185.c b/drivers/media/i2c/saa7185.c index 266462325d30..c78f2e95ba37 100644 --- a/drivers/media/i2c/saa7185.c +++ b/drivers/media/i2c/saa7185.c @@ -290,8 +290,7 @@ static const struct v4l2_subdev_ops saa7185_ops = { /* ----------------------------------------------------------------------- */ -static int saa7185_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7185_probe(struct i2c_client *client) { int i; struct saa7185 *encoder; @@ -344,7 +343,7 @@ static struct i2c_driver saa7185_driver = { .driver = { .name = "saa7185", }, - .probe = saa7185_probe, + .probe_new = saa7185_probe, .remove = saa7185_remove, .id_table = saa7185_id, }; diff --git a/drivers/media/i2c/sony-btf-mpx.c b/drivers/media/i2c/sony-btf-mpx.c index 927a9ec41463..eef6c8a7c9c9 100644 --- a/drivers/media/i2c/sony-btf-mpx.c +++ b/drivers/media/i2c/sony-btf-mpx.c @@ -331,8 +331,7 @@ static const struct v4l2_subdev_ops sony_btf_mpx_ops = { /* --------------------------------------------------------------------------*/ -static int sony_btf_mpx_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int sony_btf_mpx_probe(struct i2c_client *client) { struct sony_btf_mpx *t; struct v4l2_subdev *sd; @@ -376,7 +375,7 @@ static struct i2c_driver sony_btf_mpx_driver = { .driver = { .name = "sony-btf-mpx", }, - .probe = sony_btf_mpx_probe, + .probe_new = sony_btf_mpx_probe, .remove = sony_btf_mpx_remove, .id_table = sony_btf_mpx_id, }; diff --git a/drivers/media/i2c/sr030pc30.c b/drivers/media/i2c/sr030pc30.c index ff18693beb5c..a83c8bf1c5dd 100644 --- a/drivers/media/i2c/sr030pc30.c +++ b/drivers/media/i2c/sr030pc30.c @@ -675,8 +675,7 @@ static int sr030pc30_detect(struct i2c_client *client) } -static int sr030pc30_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int sr030pc30_probe(struct i2c_client *client) { struct sr030pc30_info *info; struct v4l2_subdev *sd; @@ -751,7 +750,7 @@ static struct i2c_driver sr030pc30_i2c_driver = { .driver = { .name = MODULE_NAME }, - .probe = sr030pc30_probe, + .probe_new = sr030pc30_probe, .remove = sr030pc30_remove, .id_table = sr030pc30_id, }; diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c new file mode 100644 index 000000000000..826baf4e064d --- /dev/null +++ b/drivers/media/i2c/st-vgxy61.c @@ -0,0 +1,1963 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for VGXY61 global shutter sensor family driver + * + * Copyright (C) 2022 STMicroelectronics SA + */ + +#include <asm-generic/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/units.h> +#include <media/mipi-csi2.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define VGXY61_REG_8BIT(n) ((1 << 16) | (n)) +#define VGXY61_REG_16BIT(n) ((2 << 16) | (n)) +#define VGXY61_REG_32BIT(n) ((4 << 16) | (n)) +#define VGXY61_REG_SIZE_SHIFT 16 +#define VGXY61_REG_ADDR_MASK 0xffff + +#define VGXY61_REG_MODEL_ID VGXY61_REG_16BIT(0x0000) +#define VG5661_MODEL_ID 0x5661 +#define VG5761_MODEL_ID 0x5761 +#define VGXY61_REG_REVISION VGXY61_REG_16BIT(0x0002) +#define VGXY61_REG_FWPATCH_REVISION VGXY61_REG_16BIT(0x0014) +#define VGXY61_REG_FWPATCH_START_ADDR VGXY61_REG_8BIT(0x2000) +#define VGXY61_REG_SYSTEM_FSM VGXY61_REG_8BIT(0x0020) +#define VGXY61_SYSTEM_FSM_SW_STBY 0x03 +#define VGXY61_SYSTEM_FSM_STREAMING 0x04 +#define VGXY61_REG_NVM VGXY61_REG_8BIT(0x0023) +#define VGXY61_NVM_OK 0x04 +#define VGXY61_REG_STBY VGXY61_REG_8BIT(0x0201) +#define VGXY61_STBY_NO_REQ 0 +#define VGXY61_STBY_REQ_TMP_READ BIT(2) +#define VGXY61_REG_STREAMING VGXY61_REG_8BIT(0x0202) +#define VGXY61_STREAMING_NO_REQ 0 +#define VGXY61_STREAMING_REQ_STOP BIT(0) +#define VGXY61_STREAMING_REQ_START BIT(1) +#define VGXY61_REG_EXT_CLOCK VGXY61_REG_32BIT(0x0220) +#define VGXY61_REG_CLK_PLL_PREDIV VGXY61_REG_8BIT(0x0224) +#define VGXY61_REG_CLK_SYS_PLL_MULT VGXY61_REG_8BIT(0x0225) +#define VGXY61_REG_GPIO_0_CTRL VGXY61_REG_8BIT(0x0236) +#define VGXY61_REG_GPIO_1_CTRL VGXY61_REG_8BIT(0x0237) +#define VGXY61_REG_GPIO_2_CTRL VGXY61_REG_8BIT(0x0238) +#define VGXY61_REG_GPIO_3_CTRL VGXY61_REG_8BIT(0x0239) +#define VGXY61_REG_SIGNALS_POLARITY_CTRL VGXY61_REG_8BIT(0x023b) +#define VGXY61_REG_LINE_LENGTH VGXY61_REG_16BIT(0x0300) +#define VGXY61_REG_ORIENTATION VGXY61_REG_8BIT(0x0302) +#define VGXY61_REG_VT_CTRL VGXY61_REG_8BIT(0x0304) +#define VGXY61_REG_FORMAT_CTRL VGXY61_REG_8BIT(0x0305) +#define VGXY61_REG_OIF_CTRL VGXY61_REG_16BIT(0x0306) +#define VGXY61_REG_OIF_ROI0_CTRL VGXY61_REG_8BIT(0x030a) +#define VGXY61_REG_ROI0_START_H VGXY61_REG_16BIT(0x0400) +#define VGXY61_REG_ROI0_START_V VGXY61_REG_16BIT(0x0402) +#define VGXY61_REG_ROI0_END_H VGXY61_REG_16BIT(0x0404) +#define VGXY61_REG_ROI0_END_V VGXY61_REG_16BIT(0x0406) +#define VGXY61_REG_PATGEN_CTRL VGXY61_REG_32BIT(0x0440) +#define VGXY61_PATGEN_LONG_ENABLE BIT(16) +#define VGXY61_PATGEN_SHORT_ENABLE BIT(0) +#define VGXY61_PATGEN_LONG_TYPE_SHIFT 18 +#define VGXY61_PATGEN_SHORT_TYPE_SHIFT 4 +#define VGXY61_REG_FRAME_CONTENT_CTRL VGXY61_REG_8BIT(0x0478) +#define VGXY61_REG_COARSE_EXPOSURE_LONG VGXY61_REG_16BIT(0x0500) +#define VGXY61_REG_COARSE_EXPOSURE_SHORT VGXY61_REG_16BIT(0x0504) +#define VGXY61_REG_ANALOG_GAIN VGXY61_REG_8BIT(0x0508) +#define VGXY61_REG_DIGITAL_GAIN_LONG VGXY61_REG_16BIT(0x050a) +#define VGXY61_REG_DIGITAL_GAIN_SHORT VGXY61_REG_16BIT(0x0512) +#define VGXY61_REG_FRAME_LENGTH VGXY61_REG_16BIT(0x051a) +#define VGXY61_REG_SIGNALS_CTRL VGXY61_REG_16BIT(0x0522) +#define VGXY61_SIGNALS_GPIO_ID_SHIFT 4 +#define VGXY61_REG_READOUT_CTRL VGXY61_REG_8BIT(0x0530) +#define VGXY61_REG_HDR_CTRL VGXY61_REG_8BIT(0x0532) +#define VGXY61_REG_PATGEN_LONG_DATA_GR VGXY61_REG_16BIT(0x092c) +#define VGXY61_REG_PATGEN_LONG_DATA_R VGXY61_REG_16BIT(0x092e) +#define VGXY61_REG_PATGEN_LONG_DATA_B VGXY61_REG_16BIT(0x0930) +#define VGXY61_REG_PATGEN_LONG_DATA_GB VGXY61_REG_16BIT(0x0932) +#define VGXY61_REG_PATGEN_SHORT_DATA_GR VGXY61_REG_16BIT(0x0950) +#define VGXY61_REG_PATGEN_SHORT_DATA_R VGXY61_REG_16BIT(0x0952) +#define VGXY61_REG_PATGEN_SHORT_DATA_B VGXY61_REG_16BIT(0x0954) +#define VGXY61_REG_PATGEN_SHORT_DATA_GB VGXY61_REG_16BIT(0x0956) +#define VGXY61_REG_BYPASS_CTRL VGXY61_REG_8BIT(0x0a60) + +#define VGX661_WIDTH 1464 +#define VGX661_HEIGHT 1104 +#define VGX761_WIDTH 1944 +#define VGX761_HEIGHT 1204 +#define VGX661_DEFAULT_MODE 1 +#define VGX761_DEFAULT_MODE 1 +#define VGX661_SHORT_ROT_TERM 93 +#define VGX761_SHORT_ROT_TERM 90 +#define VGXY61_EXPOS_ROT_TERM 66 +#define VGXY61_WRITE_MULTIPLE_CHUNK_MAX 16 +#define VGXY61_NB_GPIOS 4 +#define VGXY61_NB_POLARITIES 5 +#define VGXY61_FRAME_LENGTH_DEF 1313 +#define VGXY61_MIN_FRAME_LENGTH 1288 +#define VGXY61_MIN_EXPOSURE 10 +#define VGXY61_HDR_LINEAR_RATIO 10 +#define VGXY61_TIMEOUT_MS 500 +#define VGXY61_MEDIA_BUS_FMT_DEF MEDIA_BUS_FMT_Y8_1X8 + +#define VGXY61_FWPATCH_REVISION_MAJOR 2 +#define VGXY61_FWPATCH_REVISION_MINOR 0 +#define VGXY61_FWPATCH_REVISION_MICRO 5 + +static const u8 patch_array[] = { + 0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45, + 0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c, + 0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0, + 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1, + 0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40, + 0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0, + 0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41, + 0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80, + 0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80, + 0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0, + 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c, + 0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03, + 0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, + 0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0, + 0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, + 0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c, + 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d, + 0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84, + 0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d, + 0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02, + 0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c, + 0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1, + 0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, + 0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, + 0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40, + 0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0, + 0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1, + 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40, + 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80, + 0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80, + 0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02, + 0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00, + 0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1, + 0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1, + 0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef, + 0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53, + 0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8, + 0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55, + 0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef, + 0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c, + 0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64, + 0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0, + 0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63, + 0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c, + 0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0, + 0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c, + 0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44, + 0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04, + 0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40, + 0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54, + 0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89, + 0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00, + 0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85, + 0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83, + 0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83, + 0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42, + 0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54, + 0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54, + 0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02, + 0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0, + 0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82, + 0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70, + 0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90, + 0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00, + 0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0, + 0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00, + 0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52, + 0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00, +}; + +static const char * const vgxy61_test_pattern_menu[] = { + "Disabled", + "Solid", + "Colorbar", + "Gradbar", + "Hgrey", + "Vgrey", + "Dgrey", + "PN28", +}; + +static const char * const vgxy61_hdr_mode_menu[] = { + "HDR linearize", + "HDR substraction", + "No HDR", +}; + +static const char * const vgxy61_supply_name[] = { + "VCORE", + "VDDIO", + "VANA", +}; + +static const s64 link_freq[] = { + /* + * MIPI output freq is 804Mhz / 2, as it uses both rising edge and + * falling edges to send data + */ + 402000000ULL +}; + +enum vgxy61_bin_mode { + VGXY61_BIN_MODE_NORMAL, + VGXY61_BIN_MODE_DIGITAL_X2, + VGXY61_BIN_MODE_DIGITAL_X4, +}; + +enum vgxy61_hdr_mode { + VGXY61_HDR_LINEAR, + VGXY61_HDR_SUB, + VGXY61_NO_HDR, +}; + +enum vgxy61_strobe_mode { + VGXY61_STROBE_DISABLED, + VGXY61_STROBE_LONG, + VGXY61_STROBE_ENABLED, +}; + +struct vgxy61_mode_info { + u32 width; + u32 height; + enum vgxy61_bin_mode bin_mode; + struct v4l2_rect crop; +}; + +struct vgxy61_fmt_desc { + u32 code; + u8 bpp; + u8 data_type; +}; + +static const struct vgxy61_fmt_desc vgxy61_supported_codes[] = { + { + .code = MEDIA_BUS_FMT_Y8_1X8, + .bpp = 8, + .data_type = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_Y10_1X10, + .bpp = 10, + .data_type = MIPI_CSI2_DT_RAW10, + }, + { + .code = MEDIA_BUS_FMT_Y12_1X12, + .bpp = 12, + .data_type = MIPI_CSI2_DT_RAW12, + }, + { + .code = MEDIA_BUS_FMT_Y14_1X14, + .bpp = 14, + .data_type = MIPI_CSI2_DT_RAW14, + }, + { + .code = MEDIA_BUS_FMT_Y16_1X16, + .bpp = 16, + .data_type = MIPI_CSI2_DT_RAW16, + }, +}; + +static const struct vgxy61_mode_info vgx661_mode_data[] = { + { + .width = VGX661_WIDTH, + .height = VGX661_HEIGHT, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 0, + .top = 0, + .width = VGX661_WIDTH, + .height = VGX661_HEIGHT, + }, + }, + { + .width = 1280, + .height = 720, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 92, + .top = 192, + .width = 1280, + .height = 720, + }, + }, + { + .width = 640, + .height = 480, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, + .crop = { + .left = 92, + .top = 72, + .width = 1280, + .height = 960, + }, + }, + { + .width = 320, + .height = 240, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, + .crop = { + .left = 92, + .top = 72, + .width = 1280, + .height = 960, + }, + }, +}; + +static const struct vgxy61_mode_info vgx761_mode_data[] = { + { + .width = VGX761_WIDTH, + .height = VGX761_HEIGHT, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 0, + .top = 0, + .width = VGX761_WIDTH, + .height = VGX761_HEIGHT, + }, + }, + { + .width = 1920, + .height = 1080, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 12, + .top = 62, + .width = 1920, + .height = 1080, + }, + }, + { + .width = 1280, + .height = 720, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 332, + .top = 242, + .width = 1280, + .height = 720, + }, + }, + { + .width = 640, + .height = 480, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, + .crop = { + .left = 332, + .top = 122, + .width = 1280, + .height = 960, + }, + }, + { + .width = 320, + .height = 240, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, + .crop = { + .left = 332, + .top = 122, + .width = 1280, + .height = 960, + }, + }, +}; + +struct vgxy61_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct media_pad pad; + struct regulator_bulk_data supplies[ARRAY_SIZE(vgxy61_supply_name)]; + struct gpio_desc *reset_gpio; + struct clk *xclk; + u32 clk_freq; + u16 id; + u16 sensor_width; + u16 sensor_height; + u16 oif_ctrl; + unsigned int nb_of_lane; + u32 data_rate_in_mbps; + u32 pclk; + u16 line_length; + u16 rot_term; + bool gpios_polarity; + /* Lock to protect all members below */ + struct mutex lock; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *pixel_rate_ctrl; + struct v4l2_ctrl *expo_ctrl; + struct v4l2_ctrl *vblank_ctrl; + struct v4l2_ctrl *vflip_ctrl; + struct v4l2_ctrl *hflip_ctrl; + bool streaming; + struct v4l2_mbus_framefmt fmt; + const struct vgxy61_mode_info *sensor_modes; + unsigned int sensor_modes_nb; + const struct vgxy61_mode_info *default_mode; + const struct vgxy61_mode_info *current_mode; + bool hflip; + bool vflip; + enum vgxy61_hdr_mode hdr; + u16 expo_long; + u16 expo_short; + u16 expo_max; + u16 expo_min; + u16 vblank; + u16 vblank_min; + u16 frame_length; + u16 digital_gain; + u8 analog_gain; + enum vgxy61_strobe_mode strobe_mode; + u32 pattern; +}; + +static u8 get_bpp_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { + if (vgxy61_supported_codes[i].code == code) + return vgxy61_supported_codes[i].bpp; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to 8 bpp", code); + return 8; +} + +static u8 get_data_type_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { + if (vgxy61_supported_codes[i].code == code) + return vgxy61_supported_codes[i].data_type; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", + code); + return MIPI_CSI2_DT_RAW8; +} + +static void compute_pll_parameters_by_freq(u32 freq, u8 *prediv, u8 *mult) +{ + const unsigned int predivs[] = {1, 2, 4}; + unsigned int i; + + /* + * Freq range is [6Mhz-27Mhz] already checked. + * Output of divider should be in [6Mhz-12Mhz[. + */ + for (i = 0; i < ARRAY_SIZE(predivs); i++) { + *prediv = predivs[i]; + if (freq / *prediv < 12 * HZ_PER_MHZ) + break; + } + WARN_ON(i == ARRAY_SIZE(predivs)); + + /* + * Target freq is 804Mhz. Don't change this as it will impact image + * quality. + */ + *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq; +} + +static s32 get_pixel_rate(struct vgxy61_dev *sensor) +{ + return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane, + get_bpp_by_code(sensor->fmt.code)); +} + +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct vgxy61_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct vgxy61_dev, + ctrl_handler)->sd; +} + +static unsigned int get_chunk_size(struct vgxy61_dev *sensor) +{ + struct i2c_adapter *adapter = sensor->i2c_client->adapter; + int max_write_len = VGXY61_WRITE_MULTIPLE_CHUNK_MAX; + + if (adapter->quirks && adapter->quirks->max_write_len) + max_write_len = adapter->quirks->max_write_len - 2; + + max_write_len = min(max_write_len, VGXY61_WRITE_MULTIPLE_CHUNK_MAX); + + return max(max_write_len, 1); +} + +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u32 reg, + unsigned int len) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg[2]; + u8 buf[2]; + u8 val[sizeof(u32)] = {0}; + int ret; + + if (len > sizeof(u32)) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = val; + msg[1].len = len; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", + __func__, client->addr, reg, ret); + return ret; + } + + return get_unaligned_le32(val); +} + +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u32 reg) +{ + return vgxy61_read_multiple(sensor, reg & VGXY61_REG_ADDR_MASK, + (reg >> VGXY61_REG_SIZE_SHIFT) & 7); +} + +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u32 reg, + const u8 *data, unsigned int len, int *err) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2]; + unsigned int i; + int ret; + + if (err && *err) + return *err; + + if (len > VGXY61_WRITE_MULTIPLE_CHUNK_MAX) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + for (i = 0; i < len; i++) + buf[i + 2] = data[i]; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = len + 2; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", + __func__, reg, ret); + if (err) + *err = ret; + return ret; + } + + return 0; +} + +static int vgxy61_write_array(struct vgxy61_dev *sensor, u32 reg, + unsigned int nb, const u8 *array) +{ + const unsigned int chunk_size = get_chunk_size(sensor); + int ret; + unsigned int sz; + + while (nb) { + sz = min(nb, chunk_size); + ret = vgxy61_write_multiple(sensor, reg, array, sz, NULL); + if (ret < 0) + return ret; + nb -= sz; + reg += sz; + array += sz; + } + + return 0; +} + +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u32 reg, u32 val, + int *err) +{ + return vgxy61_write_multiple(sensor, reg & VGXY61_REG_ADDR_MASK, + (u8 *)&val, + (reg >> VGXY61_REG_SIZE_SHIFT) & 7, err); +} + +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u32 reg, u8 poll_val, + unsigned int timeout_ms) +{ + const unsigned int loop_delay_ms = 10; + int ret; + + return read_poll_timeout(vgxy61_read_reg, ret, + ((ret < 0) || (ret == poll_val)), + loop_delay_ms * 1000, timeout_ms * 1000, + false, sensor, reg); +} + +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state, + unsigned int timeout_ms) +{ + return vgxy61_poll_reg(sensor, VGXY61_REG_SYSTEM_FSM, state, + timeout_ms); +} + +static int vgxy61_check_bw(struct vgxy61_dev *sensor) +{ + /* + * Simplification of time needed to send short packets and for the MIPI + * to add transition times (EoT, LPS, and SoT packet delimiters) needed + * by the protocol to go in low power between 2 packets of data. This + * is a mipi IP constant for the sensor. + */ + const unsigned int mipi_margin = 1056; + unsigned int binning_scale = sensor->current_mode->crop.height / + sensor->current_mode->height; + u8 bpp = get_bpp_by_code(sensor->fmt.code); + unsigned int max_bit_per_line; + unsigned int bit_per_line; + u64 line_rate; + + line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * + sensor->line_length; + max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin; + bit_per_line = (bpp * sensor->current_mode->width) / binning_scale; + + return bit_per_line > max_bit_per_line ? -EINVAL : 0; +} + +static int vgxy61_apply_exposure(struct vgxy61_dev *sensor) +{ + int ret = 0; + + /* We first set expo to zero to avoid forbidden parameters couple */ + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT, 0, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_LONG, + sensor->expo_long, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT, + sensor->expo_short, &ret); + + return ret; +} + +static int vgxy61_get_regulators(struct vgxy61_dev *sensor) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supply_name); i++) + sensor->supplies[i].supply = vgxy61_supply_name[i]; + + return devm_regulator_bulk_get(&sensor->i2c_client->dev, + ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); +} + +static int vgxy61_apply_reset(struct vgxy61_dev *sensor) +{ + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(40000, 100000); + return vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); +} + +static void vgxy61_fill_framefmt(struct vgxy61_dev *sensor, + const struct vgxy61_mode_info *mode, + struct v4l2_mbus_framefmt *fmt, u32 code) +{ + fmt->code = code; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->field = V4L2_FIELD_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + const struct vgxy61_mode_info **new_mode) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *mode = sensor->sensor_modes; + unsigned int index; + + for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) { + if (vgxy61_supported_codes[index].code == fmt->code) + break; + } + if (index == ARRAY_SIZE(vgxy61_supported_codes)) + index = 0; + + mode = v4l2_find_nearest_size(sensor->sensor_modes, + sensor->sensor_modes_nb, width, height, + fmt->width, fmt->height); + if (new_mode) + *new_mode = mode; + + vgxy61_fill_framefmt(sensor, mode, fmt, + vgxy61_supported_codes[index].code); + + return 0; +} + +static int vgxy61_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = sensor->current_mode->crop; + return 0; + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = sensor->sensor_width; + sel->r.height = sensor->sensor_height; + return 0; + } + + return -EINVAL; +} + +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(vgxy61_supported_codes)) + return -EINVAL; + + code->code = vgxy61_supported_codes[code->index].code; + + return 0; +} + +static int vgxy61_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, + format->pad); + else + fmt = &sensor->fmt; + + format->format = *fmt; + + mutex_unlock(&sensor->lock); + + return 0; +} + +static u16 vgxy61_get_vblank_min(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode hdr) +{ + u16 min_vblank = VGXY61_MIN_FRAME_LENGTH - + sensor->current_mode->crop.height; + /* Ensure the first rule of thumb can't be negative */ + u16 min_vblank_hdr = VGXY61_MIN_EXPOSURE + sensor->rot_term + 1; + + if (hdr != VGXY61_NO_HDR) + return max(min_vblank, min_vblank_hdr); + return min_vblank; +} + +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + if (fse->index >= sensor->sensor_modes_nb) + return -EINVAL; + + fse->min_width = sensor->sensor_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = sensor->sensor_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target) +{ + sensor->analog_gain = target; + + if (sensor->streaming) + return vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN, target, + NULL); + return 0; +} + +static int vgxy61_apply_digital_gain(struct vgxy61_dev *sensor, + u32 digital_gain) +{ + int ret = 0; + + /* + * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and + * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all + * four sub pixels. + */ + vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_LONG, digital_gain, + &ret); + vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_SHORT, digital_gain, + &ret); + + return ret; +} + +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target) +{ + sensor->digital_gain = target; + + if (sensor->streaming) + return vgxy61_apply_digital_gain(sensor, sensor->digital_gain); + return 0; +} + +static int vgxy61_apply_patgen(struct vgxy61_dev *sensor, u32 index) +{ + static const u8 index2val[] = { + 0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13 + }; + u32 pattern = index2val[index]; + u32 reg = (pattern << VGXY61_PATGEN_LONG_TYPE_SHIFT) | + (pattern << VGXY61_PATGEN_SHORT_TYPE_SHIFT); + + if (pattern) + reg |= VGXY61_PATGEN_LONG_ENABLE | VGXY61_PATGEN_SHORT_ENABLE; + return vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_CTRL, reg, NULL); +} + +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 pattern) +{ + sensor->pattern = pattern; + + if (sensor->streaming) + return vgxy61_apply_patgen(sensor, sensor->pattern); + return 0; +} + +static int vgxy61_apply_gpiox_strobe_mode(struct vgxy61_dev *sensor, + enum vgxy61_strobe_mode mode, + unsigned int idx) +{ + static const u8 index2val[] = {0x0, 0x1, 0x3}; + int reg; + + reg = vgxy61_read_reg(sensor, VGXY61_REG_SIGNALS_CTRL); + if (reg < 0) + return reg; + reg &= ~(0xf << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT)); + reg |= index2val[mode] << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT); + + return vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_CTRL, reg, NULL); +} + +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode hdr) +{ + unsigned int i; + int ret; + + switch (hdr) { + case VGXY61_HDR_LINEAR: + sensor->strobe_mode = VGXY61_STROBE_ENABLED; + break; + case VGXY61_HDR_SUB: + case VGXY61_NO_HDR: + sensor->strobe_mode = VGXY61_STROBE_LONG; + break; + default: + /* Should never happen */ + WARN_ON(true); + break; + } + + if (!sensor->streaming) + return 0; + + for (i = 0; i < VGXY61_NB_GPIOS; i++) { + ret = vgxy61_apply_gpiox_strobe_mode(sensor, + sensor->strobe_mode, + i); + if (ret) + return ret; + } + + return 0; +} + +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, + bool polarity) +{ + int ret = 0; + + if (sensor->streaming) + return -EBUSY; + + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_0_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_1_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_2_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_3_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_POLARITY_CTRL, polarity, + &ret); + + return ret; +} + +static u32 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, + unsigned int short_expo_ratio) +{ + u32 first_rot_max_expo, second_rot_max_expo, third_rot_max_expo; + + /* Apply sensor's rules of thumb */ + /* + * Short exposure + height must be less than frame length to avoid bad + * pixel line at the botom of the image + */ + first_rot_max_expo = + ((sensor->frame_length - sensor->current_mode->crop.height - + sensor->rot_term) * short_expo_ratio) - 1; + + /* + * Total exposition time must be less than frame length to avoid sensor + * crash + */ + second_rot_max_expo = + (((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * + short_expo_ratio) / (short_expo_ratio + 1)) - 1; + + /* + * Short exposure times 71 must be less than frame length to avoid + * sensor crash + */ + third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio; + + /* Take the minimum from all rules */ + return min(min(first_rot_max_expo, second_rot_max_expo), + third_rot_max_expo); +} + +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, u16 new_expo_long, + enum vgxy61_hdr_mode hdr) +{ + struct i2c_client *client = sensor->i2c_client; + u16 new_expo_short = 0; + u16 expo_short_max = 0; + u16 expo_long_min = VGXY61_MIN_EXPOSURE; + u16 expo_long_max = 0; + + /* Compute short exposure according to hdr mode and long exposure */ + switch (hdr) { + case VGXY61_HDR_LINEAR: + /* + * Take ratio into account for minimal exposures in + * VGXY61_HDR_LINEAR + */ + expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO; + new_expo_long = max(expo_long_min, new_expo_long); + + expo_long_max = + vgxy61_get_expo_long_max(sensor, + VGXY61_HDR_LINEAR_RATIO); + expo_short_max = (expo_long_max + + (VGXY61_HDR_LINEAR_RATIO / 2)) / + VGXY61_HDR_LINEAR_RATIO; + new_expo_short = (new_expo_long + + (VGXY61_HDR_LINEAR_RATIO / 2)) / + VGXY61_HDR_LINEAR_RATIO; + break; + case VGXY61_HDR_SUB: + new_expo_long = max(expo_long_min, new_expo_long); + + expo_long_max = vgxy61_get_expo_long_max(sensor, 1); + /* Short and long are the same in VGXY61_HDR_SUB */ + expo_short_max = expo_long_max; + new_expo_short = new_expo_long; + break; + case VGXY61_NO_HDR: + new_expo_long = max(expo_long_min, new_expo_long); + + /* + * As short expo is 0 here, only the second rule of thumb + * applies, see vgxy61_get_expo_long_max for more + */ + expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM; + break; + default: + /* Should never happen */ + WARN_ON(true); + break; + } + + /* If this happens, something is wrong with formulas */ + WARN_ON(expo_long_min > expo_long_max); + + if (new_expo_long > expo_long_max) { + dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n", + new_expo_long, expo_long_max); + new_expo_long = expo_long_max; + new_expo_short = expo_short_max; + } + + sensor->expo_long = new_expo_long; + sensor->expo_short = new_expo_short; + sensor->expo_max = expo_long_max; + sensor->expo_min = expo_long_min; + + if (sensor->streaming) + return vgxy61_apply_exposure(sensor); + return 0; +} + +static int vgxy61_apply_framelength(struct vgxy61_dev *sensor) +{ + return vgxy61_write_reg(sensor, VGXY61_REG_FRAME_LENGTH, + sensor->frame_length, NULL); +} + +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, u16 vblank, + enum vgxy61_hdr_mode hdr) +{ + int ret; + + sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr); + sensor->vblank = max(sensor->vblank_min, vblank); + sensor->frame_length = sensor->current_mode->crop.height + + sensor->vblank; + + /* Update exposure according to vblank */ + ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr); + if (ret) + return ret; + + if (sensor->streaming) + return vgxy61_apply_framelength(sensor); + return 0; +} + +static int vgxy61_apply_hdr(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode index) +{ + static const u8 index2val[] = {0x1, 0x4, 0xa}; + + return vgxy61_write_reg(sensor, VGXY61_REG_HDR_CTRL, index2val[index], + NULL); +} + +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode index) +{ + int ret; + + /* + * vblank and short exposure change according to HDR mode, do it first + * as it can violate sensors 'rule of thumbs' and therefore will require + * to change the long exposure. + */ + ret = vgxy61_update_vblank(sensor, sensor->vblank, index); + if (ret) + return ret; + + /* Update strobe mode according to HDR */ + ret = vgxy61_update_gpios_strobe_mode(sensor, index); + if (ret) + return ret; + + sensor->hdr = index; + + if (sensor->streaming) + return vgxy61_apply_hdr(sensor, sensor->hdr); + return 0; +} + +static int vgxy61_apply_settings(struct vgxy61_dev *sensor) +{ + int ret; + unsigned int i; + + ret = vgxy61_apply_hdr(sensor, sensor->hdr); + if (ret) + return ret; + + ret = vgxy61_apply_framelength(sensor); + if (ret) + return ret; + + ret = vgxy61_apply_exposure(sensor); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN, + sensor->analog_gain, NULL); + if (ret) + return ret; + ret = vgxy61_apply_digital_gain(sensor, sensor->digital_gain); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_ORIENTATION, + sensor->hflip | (sensor->vflip << 1), NULL); + if (ret) + return ret; + + ret = vgxy61_apply_patgen(sensor, sensor->pattern); + if (ret) + return ret; + + for (i = 0; i < VGXY61_NB_GPIOS; i++) { + ret = vgxy61_apply_gpiox_strobe_mode(sensor, + sensor->strobe_mode, i); + if (ret) + return ret; + } + + return 0; +} + +static int vgxy61_stream_enable(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + const struct v4l2_rect *crop = &sensor->current_mode->crop; + int ret = 0; + + ret = vgxy61_check_bw(sensor); + if (ret) + return ret; + + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(&client->dev); + return ret; + } + + /* pm_runtime_get_sync() can return 1 as a valid return code */ + ret = 0; + + vgxy61_write_reg(sensor, VGXY61_REG_FORMAT_CTRL, + get_bpp_by_code(sensor->fmt.code), &ret); + vgxy61_write_reg(sensor, VGXY61_REG_OIF_ROI0_CTRL, + get_data_type_by_code(sensor->fmt.code), &ret); + + vgxy61_write_reg(sensor, VGXY61_REG_READOUT_CTRL, + sensor->current_mode->bin_mode, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_H, crop->left, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_H, + crop->left + crop->width - 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_V, crop->top, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_V, + crop->top + crop->height - 1, &ret); + if (ret) + goto err_rpm_put; + + ret = vgxy61_apply_settings(sensor); + if (ret) + goto err_rpm_put; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_REQ_START, NULL); + if (ret) + goto err_rpm_put; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_NO_REQ, VGXY61_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_STREAMING, + VGXY61_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(sensor->vflip_ctrl, true); + __v4l2_ctrl_grab(sensor->hflip_ctrl, true); + + return 0; + +err_rpm_put: + pm_runtime_put(&client->dev); + return ret; +} + +static int vgxy61_stream_disable(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + int ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_REQ_STOP, NULL); + if (ret) + goto err_str_dis; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_NO_REQ, 2000); + if (ret) + goto err_str_dis; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); + if (ret) + goto err_str_dis; + + __v4l2_ctrl_grab(sensor->vflip_ctrl, false); + __v4l2_ctrl_grab(sensor->hflip_ctrl, false); + +err_str_dis: + if (ret) + WARN(1, "Can't disable stream"); + pm_runtime_put(&client->dev); + + return ret; +} + +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + ret = enable ? vgxy61_stream_enable(sensor) : + vgxy61_stream_disable(sensor); + if (!ret) + sensor->streaming = enable; + + mutex_unlock(&sensor->lock); + + return ret; +} + +static int vgxy61_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *new_mode; + struct v4l2_mbus_framefmt *fmt; + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto out; + } + + ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode); + if (ret) + goto out; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt = v4l2_subdev_get_try_format(sd, sd_state, 0); + *fmt = format->format; + } else if (sensor->current_mode != new_mode || + sensor->fmt.code != format->format.code) { + fmt = &sensor->fmt; + *fmt = format->format; + + sensor->current_mode = new_mode; + + /* Reset vblank and framelength to default */ + ret = vgxy61_update_vblank(sensor, + VGXY61_FRAME_LENGTH_DEF - + new_mode->crop.height, + sensor->hdr); + + /* Update controls to reflect new mode */ + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, + get_pixel_rate(sensor)); + __v4l2_ctrl_modify_range(sensor->vblank_ctrl, + sensor->vblank_min, + 0xffff - new_mode->crop.height, + 1, sensor->vblank); + __v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank); + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + } + +out: + mutex_unlock(&sensor->lock); + + return ret; +} + +static int vgxy61_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + struct v4l2_subdev_format fmt = { 0 }; + + sensor->current_mode = sensor->default_mode; + vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format, + VGXY61_MEDIA_BUS_FMT_DEF); + + return vgxy61_set_fmt(sd, sd_state, &fmt); +} + +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *cur_mode = sensor->current_mode; + int ret; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr); + ctrl->val = sensor->expo_long; + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = vgxy61_update_analog_gain(sensor, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = vgxy61_update_digital_gain(sensor, ctrl->val); + break; + case V4L2_CID_VFLIP: + case V4L2_CID_HFLIP: + if (sensor->streaming) { + ret = -EBUSY; + break; + } + if (ctrl->id == V4L2_CID_VFLIP) + sensor->vflip = ctrl->val; + if (ctrl->id == V4L2_CID_HFLIP) + sensor->hflip = ctrl->val; + ret = 0; + break; + case V4L2_CID_TEST_PATTERN: + ret = vgxy61_update_patgen(sensor, ctrl->val); + break; + case V4L2_CID_HDR_SENSOR_MODE: + ret = vgxy61_update_hdr(sensor, ctrl->val); + /* Update vblank and exposure controls to match new hdr */ + __v4l2_ctrl_modify_range(sensor->vblank_ctrl, + sensor->vblank_min, + 0xffff - cur_mode->crop.height, + 1, sensor->vblank); + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + break; + case V4L2_CID_VBLANK: + ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr); + /* Update exposure control to match new vblank */ + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = { + .s_ctrl = vgxy61_s_ctrl, +}; + +static int vgxy61_init_controls(struct vgxy61_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops; + struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler; + const struct vgxy61_mode_info *cur_mode = sensor->current_mode; + struct v4l2_ctrl *ctrl; + int ret; + + v4l2_ctrl_handler_init(hdl, 16); + /* We can use our own mutex for the ctrl lock */ + hdl->lock = &sensor->lock; + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, + sensor->analog_gain); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, + sensor->digital_gain); + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(vgxy61_test_pattern_menu) - 1, + 0, 0, vgxy61_test_pattern_menu); + ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, + sensor->line_length, 1, + sensor->line_length - cur_mode->width); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - 1, 0, link_freq); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_HDR_SENSOR_MODE, + ARRAY_SIZE(vgxy61_hdr_mode_menu) - 1, 0, + VGXY61_NO_HDR, vgxy61_hdr_mode_menu); + + /* + * Keep a pointer to these controls as we need to update them when + * setting the format + */ + sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_PIXEL_RATE, 1, + INT_MAX, 1, + get_pixel_rate(sensor)); + if (sensor->pixel_rate_ctrl) + sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + sensor->vblank_min, + 0xffff - cur_mode->crop.height, + 1, sensor->vblank); + sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, + 0, 1, 1, sensor->vflip); + sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, + 0, 1, 1, sensor->hflip); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + sensor->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static const struct v4l2_subdev_video_ops vgxy61_video_ops = { + .s_stream = vgxy61_s_stream, +}; + +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = { + .init_cfg = vgxy61_init_cfg, + .enum_mbus_code = vgxy61_enum_mbus_code, + .get_fmt = vgxy61_get_fmt, + .set_fmt = vgxy61_set_fmt, + .get_selection = vgxy61_get_selection, + .enum_frame_size = vgxy61_enum_frame_size, +}; + +static const struct v4l2_subdev_ops vgxy61_subdev_ops = { + .video = &vgxy61_video_ops, + .pad = &vgxy61_pad_ops, +}; + +static const struct media_entity_operations vgxy61_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, + struct fwnode_handle *handle) +{ + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct i2c_client *client = sensor->i2c_client; + u32 log2phy[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; + u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; + int polarities[VGXY61_NB_POLARITIES] = {0, 0, 0, 0, 0}; + int l_nb; + unsigned int p, l, i; + int ret; + + ret = v4l2_fwnode_endpoint_alloc_parse(handle, &ep); + if (ret) + return -EINVAL; + + l_nb = ep.bus.mipi_csi2.num_data_lanes; + if (l_nb != 1 && l_nb != 2 && l_nb != 4) { + dev_err(&client->dev, "invalid data lane number %d\n", l_nb); + goto error_ep; + } + + /* Build log2phy, phy2log and polarities from ep info */ + log2phy[0] = ep.bus.mipi_csi2.clock_lane; + phy2log[log2phy[0]] = 0; + for (l = 1; l < l_nb + 1; l++) { + log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1]; + phy2log[log2phy[l]] = l; + } + /* + * Then fill remaining slots for every physical slot to have something + * valid for hardware stuff. + */ + for (p = 0; p < VGXY61_NB_POLARITIES; p++) { + if (phy2log[p] != ~0) + continue; + phy2log[p] = l; + log2phy[l] = p; + l++; + } + for (l = 0; l < l_nb + 1; l++) + polarities[l] = ep.bus.mipi_csi2.lane_polarities[l]; + + if (log2phy[0] != 0) { + dev_err(&client->dev, "clk lane must be map to physical lane 0\n"); + goto error_ep; + } + sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) + + (polarities[3] << 12) + ((phy2log[3] - 1) << 10) + + (polarities[2] << 9) + ((phy2log[2] - 1) << 7) + + (polarities[1] << 6) + ((phy2log[1] - 1) << 4) + + (polarities[0] << 3) + + l_nb; + sensor->nb_of_lane = l_nb; + + dev_dbg(&client->dev, "tx uses %d lanes", l_nb); + for (i = 0; i < 5; i++) { + dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]); + dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]); + dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]); + } + dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl); + + v4l2_fwnode_endpoint_free(&ep); + + return 0; + +error_ep: + v4l2_fwnode_endpoint_free(&ep); + + return -EINVAL; +} + +static int vgxy61_configure(struct vgxy61_dev *sensor) +{ + u32 sensor_freq; + u8 prediv, mult; + int line_length; + int ret = 0; + + compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult); + sensor_freq = (mult * sensor->clk_freq) / prediv; + /* Frequency to data rate is 1:1 ratio for MIPI */ + sensor->data_rate_in_mbps = sensor_freq; + /* Video timing ISP path (pixel clock) requires 804/5 mhz = 160 mhz */ + sensor->pclk = sensor_freq / 5; + + line_length = vgxy61_read_reg(sensor, VGXY61_REG_LINE_LENGTH); + if (line_length < 0) + return line_length; + sensor->line_length = line_length; + vgxy61_write_reg(sensor, VGXY61_REG_EXT_CLOCK, sensor->clk_freq, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_CLK_PLL_PREDIV, prediv, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_CLK_SYS_PLL_MULT, mult, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_OIF_CTRL, sensor->oif_ctrl, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_FRAME_CONTENT_CTRL, 0, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_BYPASS_CTRL, 4, &ret); + if (ret) + return ret; + vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity); + /* Set pattern generator solid to middle value */ + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GR, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_R, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_B, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GB, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GR, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_R, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_B, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GB, 0x800, &ret); + if (ret) + return ret; + + return 0; +} + +static int vgxy61_patch(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int patch, ret; + + ret = vgxy61_write_array(sensor, VGXY61_REG_FWPATCH_START_ADDR, + sizeof(patch_array), patch_array); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STBY, 0x10, NULL); + if (ret) + return ret; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STBY, 0, VGXY61_TIMEOUT_MS); + if (ret) + return ret; + + patch = vgxy61_read_reg(sensor, VGXY61_REG_FWPATCH_REVISION); + if (patch < 0) + return patch; + + if (patch != (VGXY61_FWPATCH_REVISION_MAJOR << 12) + + (VGXY61_FWPATCH_REVISION_MINOR << 8) + + VGXY61_FWPATCH_REVISION_MICRO) { + dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n", + VGXY61_FWPATCH_REVISION_MAJOR, + VGXY61_FWPATCH_REVISION_MINOR, + VGXY61_FWPATCH_REVISION_MICRO, + patch >> 12, (patch >> 8) & 0x0f, patch & 0xff); + return -ENODEV; + } + dev_dbg(&client->dev, "patch %d.%d.%d applied\n", + patch >> 12, (patch >> 8) & 0x0f, patch & 0xff); + + return 0; +} + +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int device_rev; + + device_rev = vgxy61_read_reg(sensor, VGXY61_REG_REVISION); + if (device_rev < 0) + return device_rev; + + switch (device_rev >> 8) { + case 0xA: + dev_dbg(&client->dev, "Cut1 detected\n"); + dev_err(&client->dev, "Cut1 not supported by this driver\n"); + return -ENODEV; + case 0xB: + dev_dbg(&client->dev, "Cut2 detected\n"); + return 0; + case 0xC: + dev_dbg(&client->dev, "Cut3 detected\n"); + return 0; + default: + dev_err(&client->dev, "Unable to detect cut version\n"); + return -ENODEV; + } +} + +static int vgxy61_detect(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int id = 0; + int ret, st; + + id = vgxy61_read_reg(sensor, VGXY61_REG_MODEL_ID); + if (id < 0) + return id; + if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) { + dev_warn(&client->dev, "Unsupported sensor id %x\n", id); + return -ENODEV; + } + dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id); + sensor->id = id; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); + if (ret) + return ret; + + st = vgxy61_read_reg(sensor, VGXY61_REG_NVM); + if (st < 0) + return st; + if (st != VGXY61_NVM_OK) + dev_warn(&client->dev, "Bad nvm state got %d\n", st); + + ret = vgxy61_detect_cut_version(sensor); + if (ret) + return ret; + + return 0; +} + +/* Power/clock management functions */ +static int vgxy61_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + if (ret) { + dev_err(&client->dev, "failed to enable regulators %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(sensor->xclk); + if (ret) { + dev_err(&client->dev, "failed to enable clock %d\n", ret); + goto disable_bulk; + } + + if (sensor->reset_gpio) { + ret = vgxy61_apply_reset(sensor); + if (ret) { + dev_err(&client->dev, "sensor reset failed %d\n", ret); + goto disable_clock; + } + } + + ret = vgxy61_patch(sensor); + if (ret) { + dev_err(&client->dev, "sensor patch failed %d\n", ret); + goto disable_clock; + } + + ret = vgxy61_configure(sensor); + if (ret) { + dev_err(&client->dev, "sensor configuration failed %d\n", ret); + goto disable_clock; + } + + return 0; + +disable_clock: + clk_disable_unprepare(sensor->xclk); +disable_bulk: + regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + + return ret; +} + +static int vgxy61_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + clk_disable_unprepare(sensor->xclk); + regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + return 0; +} + +static void vgxy61_fill_sensor_param(struct vgxy61_dev *sensor) +{ + if (sensor->id == VG5761_MODEL_ID) { + sensor->sensor_width = VGX761_WIDTH; + sensor->sensor_height = VGX761_HEIGHT; + sensor->sensor_modes = vgx761_mode_data; + sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data); + sensor->default_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE]; + sensor->rot_term = VGX761_SHORT_ROT_TERM; + } else if (sensor->id == VG5661_MODEL_ID) { + sensor->sensor_width = VGX661_WIDTH; + sensor->sensor_height = VGX661_HEIGHT; + sensor->sensor_modes = vgx661_mode_data; + sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data); + sensor->default_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE]; + sensor->rot_term = VGX661_SHORT_ROT_TERM; + } else { + /* Should never happen */ + WARN_ON(true); + } + sensor->current_mode = sensor->default_mode; +} + +static int vgxy61_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *handle; + struct vgxy61_dev *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->i2c_client = client; + sensor->streaming = false; + sensor->hdr = VGXY61_NO_HDR; + sensor->expo_long = 200; + sensor->expo_short = 0; + sensor->hflip = false; + sensor->vflip = false; + sensor->analog_gain = 0; + sensor->digital_gain = 256; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!handle) { + dev_err(dev, "handle node not found\n"); + return -EINVAL; + } + + ret = vgxy61_tx_from_ep(sensor, handle); + fwnode_handle_put(handle); + if (ret) { + dev_err(dev, "Failed to parse handle %d\n", ret); + return ret; + } + + sensor->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(sensor->xclk); + } + sensor->clk_freq = clk_get_rate(sensor->xclk); + if (sensor->clk_freq < 6 * HZ_PER_MHZ || + sensor->clk_freq > 27 * HZ_PER_MHZ) { + dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n", + sensor->clk_freq / HZ_PER_MHZ); + return -EINVAL; + } + sensor->gpios_polarity = + device_property_read_bool(dev, "st,strobe-gpios-polarity"); + + v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops); + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.ops = &vgxy61_subdev_entity_ops; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + ret = vgxy61_get_regulators(sensor); + if (ret) { + dev_err(&client->dev, "failed to get regulators %d\n", ret); + return ret; + } + + ret = vgxy61_power_on(dev); + if (ret) + return ret; + + ret = vgxy61_detect(sensor); + if (ret) { + dev_err(&client->dev, "sensor detect failed %d\n", ret); + return ret; + } + + vgxy61_fill_sensor_param(sensor); + vgxy61_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt, + VGXY61_MEDIA_BUS_FMT_DEF); + + ret = vgxy61_update_hdr(sensor, sensor->hdr); + if (ret) + return ret; + + mutex_init(&sensor->lock); + + ret = vgxy61_init_controls(sensor); + if (ret) { + dev_err(&client->dev, "controls initialization failed %d\n", + ret); + goto error_power_off; + } + + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) { + dev_err(&client->dev, "pads init failed %d\n", ret); + goto error_handler_free; + } + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret) { + dev_err(&client->dev, "async subdev register failed %d\n", ret); + goto error_pm_runtime; + } + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + dev_dbg(&client->dev, "vgxy61 probe successfully\n"); + + return 0; + +error_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + media_entity_cleanup(&sensor->sd.entity); +error_handler_free: + v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); + mutex_destroy(&sensor->lock); +error_power_off: + vgxy61_power_off(dev); + + return ret; +} + +static void vgxy61_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + mutex_destroy(&sensor->lock); + media_entity_cleanup(&sensor->sd.entity); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + vgxy61_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id vgxy61_dt_ids[] = { + { .compatible = "st,st-vgxy61" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids); + +static const struct dev_pm_ops vgxy61_pm_ops = { + SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL) +}; + +static struct i2c_driver vgxy61_i2c_driver = { + .driver = { + .name = "st-vgxy61", + .of_match_table = vgxy61_dt_ids, + .pm = &vgxy61_pm_ops, + }, + .probe_new = vgxy61_probe, + .remove = vgxy61_remove, +}; + +module_i2c_driver(vgxy61_i2c_driver); + +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>"); +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>"); +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>"); +MODULE_DESCRIPTION("VGXY61 camera subdev driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index 200841c1f5cf..9197fa0b1bc2 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -1891,12 +1891,9 @@ static int tc358743_probe_of(struct tc358743_state *state) int ret; refclk = devm_clk_get(dev, "refclk"); - if (IS_ERR(refclk)) { - if (PTR_ERR(refclk) != -EPROBE_DEFER) - dev_err(dev, "failed to get refclk: %ld\n", - PTR_ERR(refclk)); - return PTR_ERR(refclk); - } + if (IS_ERR(refclk)) + return dev_err_probe(dev, PTR_ERR(refclk), + "failed to get refclk\n"); ep = of_graph_get_next_endpoint(dev->of_node, NULL); if (!ep) { diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c new file mode 100644 index 000000000000..d1f552bd81d4 --- /dev/null +++ b/drivers/media/i2c/tc358746.c @@ -0,0 +1,1694 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TC358746 - Parallel <-> CSI-2 Bridge + * + * Copyright 2022 Marco Felsch <kernel@pengutronix.de> + * + * Notes: + * - Currently only 'Parallel-in -> CSI-out' mode is supported! + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/phy/phy-mipi-dphy.h> +#include <linux/property.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/units.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> + +/* 16-bit registers */ +#define CHIPID_REG 0x0000 +#define CHIPID GENMASK(15, 8) + +#define SYSCTL_REG 0x0002 +#define SRESET BIT(0) + +#define CONFCTL_REG 0x0004 +#define PDATAF_MASK GENMASK(9, 8) +#define PDATAF_MODE0 0 +#define PDATAF_MODE1 1 +#define PDATAF_MODE2 2 +#define PDATAF(val) FIELD_PREP(PDATAF_MASK, (val)) +#define PPEN BIT(6) +#define DATALANE_MASK GENMASK(1, 0) + +#define FIFOCTL_REG 0x0006 +#define DATAFMT_REG 0x0008 +#define PDFMT(val) FIELD_PREP(GENMASK(7, 4), (val)) + +#define MCLKCTL_REG 0x000c +#define MCLK_HIGH_MASK GENMASK(15, 8) +#define MCLK_LOW_MASK GENMASK(7, 0) +#define MCLK_HIGH(val) FIELD_PREP(MCLK_HIGH_MASK, (val)) +#define MCLK_LOW(val) FIELD_PREP(MCLK_LOW_MASK, (val)) + +#define PLLCTL0_REG 0x0016 +#define PLL_PRD_MASK GENMASK(15, 12) +#define PLL_PRD(val) FIELD_PREP(PLL_PRD_MASK, (val)) +#define PLL_FBD_MASK GENMASK(8, 0) +#define PLL_FBD(val) FIELD_PREP(PLL_FBD_MASK, (val)) + +#define PLLCTL1_REG 0x0018 +#define PLL_FRS_MASK GENMASK(11, 10) +#define PLL_FRS(val) FIELD_PREP(PLL_FRS_MASK, (val)) +#define CKEN BIT(4) +#define RESETB BIT(1) +#define PLL_EN BIT(0) + +#define CLKCTL_REG 0x0020 +#define MCLKDIV_MASK GENMASK(3, 2) +#define MCLKDIV(val) FIELD_PREP(MCLKDIV_MASK, (val)) +#define MCLKDIV_8 0 +#define MCLKDIV_4 1 +#define MCLKDIV_2 2 + +#define WORDCNT_REG 0x0022 +#define PP_MISC_REG 0x0032 +#define FRMSTOP BIT(15) +#define RSTPTR BIT(14) + +/* 32-bit registers */ +#define CLW_DPHYCONTTX_REG 0x0100 +#define CLW_CNTRL_REG 0x0140 +#define D0W_CNTRL_REG 0x0144 +#define LANEDISABLE BIT(0) + +#define STARTCNTRL_REG 0x0204 +#define START BIT(0) + +#define LINEINITCNT_REG 0x0210 +#define LPTXTIMECNT_REG 0x0214 +#define TCLK_HEADERCNT_REG 0x0218 +#define TCLK_ZEROCNT(val) FIELD_PREP(GENMASK(15, 8), (val)) +#define TCLK_PREPARECNT(val) FIELD_PREP(GENMASK(6, 0), (val)) + +#define TCLK_TRAILCNT_REG 0x021C +#define THS_HEADERCNT_REG 0x0220 +#define THS_ZEROCNT(val) FIELD_PREP(GENMASK(14, 8), (val)) +#define THS_PREPARECNT(val) FIELD_PREP(GENMASK(6, 0), (val)) + +#define TWAKEUP_REG 0x0224 +#define TCLK_POSTCNT_REG 0x0228 +#define THS_TRAILCNT_REG 0x022C +#define HSTXVREGEN_REG 0x0234 +#define TXOPTIONCNTRL_REG 0x0238 +#define CSI_CONTROL_REG 0x040C +#define CSI_MODE BIT(15) +#define TXHSMD BIT(7) +#define NOL(val) FIELD_PREP(GENMASK(2, 1), (val)) + +#define CSI_CONFW_REG 0x0500 +#define MODE(val) FIELD_PREP(GENMASK(31, 29), (val)) +#define MODE_SET 0x5 +#define ADDRESS(val) FIELD_PREP(GENMASK(28, 24), (val)) +#define CSI_CONTROL_ADDRESS 0x3 +#define DATA(val) FIELD_PREP(GENMASK(15, 0), (val)) + +#define CSI_START_REG 0x0518 +#define STRT BIT(0) + +static const struct v4l2_mbus_framefmt tc358746_def_fmt = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + +static const char * const tc358746_supplies[] = { + "vddc", "vddio", "vddmipi" +}; + +enum { + TC358746_SINK, + TC358746_SOURCE, + TC358746_NR_PADS +}; + +struct tc358746 { + struct v4l2_subdev sd; + struct media_pad pads[TC358746_NR_PADS]; + struct v4l2_async_notifier notifier; + struct v4l2_fwnode_endpoint csi_vep; + + struct v4l2_ctrl_handler ctrl_hdl; + + struct regmap *regmap; + struct clk *refclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(tc358746_supplies)]; + + struct clk_hw mclk_hw; + unsigned long mclk_rate; + u8 mclk_prediv; + u16 mclk_postdiv; + + unsigned long pll_rate; + u8 pll_post_div; + u16 pll_pre_div; + u16 pll_mul; + +#define TC358746_VB_MAX_SIZE (511 * 32) +#define TC358746_VB_DEFAULT_SIZE (1 * 32) + unsigned int vb_size; /* Video buffer size in bits */ + + struct phy_configure_opts_mipi_dphy dphy_cfg; +}; + +static inline struct tc358746 *to_tc358746(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tc358746, sd); +} + +static inline struct tc358746 *clk_hw_to_tc358746(struct clk_hw *hw) +{ + return container_of(hw, struct tc358746, mclk_hw); +} + +struct tc358746_format { + u32 code; + bool csi_format; + unsigned char bus_width; + unsigned char bpp; + /* Register values */ + u8 pdformat; /* Peripheral Data Format */ + u8 pdataf; /* Parallel Data Format Option */ +}; + +enum { + PDFORMAT_RAW8 = 0, + PDFORMAT_RAW10, + PDFORMAT_RAW12, + PDFORMAT_RGB888, + PDFORMAT_RGB666, + PDFORMAT_RGB565, + PDFORMAT_YUV422_8BIT, + /* RESERVED = 7 */ + PDFORMAT_RAW14 = 8, + PDFORMAT_YUV422_10BIT, + PDFORMAT_YUV444, +}; + +/* Check tc358746_src_mbus_code() if you add new formats */ +static const struct tc358746_format tc358746_formats[] = { + { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .bus_width = 8, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE0, + }, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .csi_format = true, + .bus_width = 16, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE1, + }, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .csi_format = true, + .bus_width = 16, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE2, + }, { + .code = MEDIA_BUS_FMT_UYVY10_2X10, + .bus_width = 10, + .bpp = 20, + .pdformat = PDFORMAT_YUV422_10BIT, + .pdataf = PDATAF_MODE0, /* don't care */ + } +}; + +/* Get n-th format for pad */ +static const struct tc358746_format * +tc358746_get_format_by_idx(unsigned int pad, unsigned int index) +{ + unsigned int idx = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) { + const struct tc358746_format *fmt = &tc358746_formats[i]; + + if ((pad == TC358746_SOURCE && fmt->csi_format) || + (pad == TC358746_SINK)) { + if (idx == index) + return fmt; + idx++; + } + } + + return ERR_PTR(-EINVAL); +} + +static const struct tc358746_format * +tc358746_get_format_by_code(unsigned int pad, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) { + const struct tc358746_format *fmt = &tc358746_formats[i]; + + if (pad == TC358746_SINK && fmt->code == code) + return fmt; + + if (pad == TC358746_SOURCE && !fmt->csi_format) + continue; + + if (fmt->code == code) + return fmt; + } + + return ERR_PTR(-EINVAL); +} + +static u32 tc358746_src_mbus_code(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + return MEDIA_BUS_FMT_UYVY8_1X16; + case MEDIA_BUS_FMT_UYVY10_2X10: + return MEDIA_BUS_FMT_UYVY10_1X20; + default: + return code; + } +} + +static bool tc358746_valid_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CHIPID_REG ... CSI_START_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config tc358746_regmap_config = { + .name = "tc358746", + .reg_bits = 16, + .val_bits = 16, + .max_register = CSI_START_REG, + .writeable_reg = tc358746_valid_reg, + .readable_reg = tc358746_valid_reg, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int tc358746_write(struct tc358746 *tc358746, u32 reg, u32 val) +{ + size_t count; + int err; + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + count = reg < CLW_DPHYCONTTX_REG ? 1 : 2; + + err = regmap_bulk_write(tc358746->regmap, reg, &val, count); + if (err) + dev_err(tc358746->sd.dev, + "Failed to write reg:0x%04x err:%d\n", reg, err); + + return err; +} + +static int tc358746_read(struct tc358746 *tc358746, u32 reg, u32 *val) +{ + size_t count; + int err; + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + count = reg < CLW_DPHYCONTTX_REG ? 1 : 2; + *val = 0; + + err = regmap_bulk_read(tc358746->regmap, reg, val, count); + if (err) + dev_err(tc358746->sd.dev, + "Failed to read reg:0x%04x err:%d\n", reg, err); + + return err; +} + +static int +tc358746_update_bits(struct tc358746 *tc358746, u32 reg, u32 mask, u32 val) +{ + u32 tmp, orig; + int err; + + err = tc358746_read(tc358746, reg, &orig); + if (err) + return err; + + tmp = orig & ~mask; + tmp |= val & mask; + + return tc358746_write(tc358746, reg, tmp); +} + +static int tc358746_set_bits(struct tc358746 *tc358746, u32 reg, u32 bits) +{ + return tc358746_update_bits(tc358746, reg, bits, bits); +} + +static int tc358746_clear_bits(struct tc358746 *tc358746, u32 reg, u32 bits) +{ + return tc358746_update_bits(tc358746, reg, bits, 0); +} + +static int tc358746_sw_reset(struct tc358746 *tc358746) +{ + int err; + + err = tc358746_set_bits(tc358746, SYSCTL_REG, SRESET); + if (err) + return err; + + fsleep(10); + + return tc358746_clear_bits(tc358746, SYSCTL_REG, SRESET); +} + +static int +tc358746_apply_pll_config(struct tc358746 *tc358746) +{ + u8 post = tc358746->pll_post_div; + u16 pre = tc358746->pll_pre_div; + u16 mul = tc358746->pll_mul; + u32 val, mask; + int err; + + err = tc358746_read(tc358746, PLLCTL1_REG, &val); + if (err) + return err; + + /* Don't touch the PLL if running */ + if (FIELD_GET(PLL_EN, val) == 1) + return 0; + + /* Pre-div and Multiplicator have a internal +1 logic */ + val = PLL_PRD(pre - 1) | PLL_FBD(mul - 1); + mask = PLL_PRD_MASK | PLL_FBD_MASK; + err = tc358746_update_bits(tc358746, PLLCTL0_REG, mask, val); + if (err) + return err; + + val = PLL_FRS(ilog2(post)) | RESETB | PLL_EN; + mask = PLL_FRS_MASK | RESETB | PLL_EN; + tc358746_update_bits(tc358746, PLLCTL1_REG, mask, val); + if (err) + return err; + + fsleep(1000); + + return tc358746_set_bits(tc358746, PLLCTL1_REG, CKEN); +} + +static int tc358746_apply_misc_config(struct tc358746 *tc358746) +{ + const struct v4l2_mbus_framefmt *mbusfmt; + struct v4l2_subdev *sd = &tc358746->sd; + struct v4l2_subdev_state *sink_state; + const struct tc358746_format *fmt; + struct device *dev = sd->dev; + u32 val; + int err; + + sink_state = v4l2_subdev_lock_and_get_active_state(sd); + + mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK); + fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code); + + /* Self defined CSI user data type id's are not supported yet */ + val = PDFMT(fmt->pdformat); + dev_dbg(dev, "DATAFMT: 0x%x\n", val); + err = tc358746_write(tc358746, DATAFMT_REG, val); + if (err) + goto out; + + val = PDATAF(fmt->pdataf); + dev_dbg(dev, "CONFCTL[PDATAF]: 0x%x\n", fmt->pdataf); + err = tc358746_update_bits(tc358746, CONFCTL_REG, PDATAF_MASK, val); + if (err) + goto out; + + val = tc358746->vb_size / 32; + dev_dbg(dev, "FIFOCTL: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, FIFOCTL_REG, val); + if (err) + goto out; + + /* Total number of bytes for each line/width */ + val = mbusfmt->width * fmt->bpp / 8; + dev_dbg(dev, "WORDCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, WORDCNT_REG, val); + +out: + v4l2_subdev_unlock_state(sink_state); + + return err; +} + +/* Use MHz as base so the div needs no u64 */ +static u32 tc358746_cfg_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz, + unsigned int time_base) +{ + return DIV_ROUND_UP(cfg_val * clk_mhz, time_base); +} + +static u32 tc358746_ps_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz) +{ + return tc358746_cfg_to_cnt(cfg_val, clk_mhz, USEC_PER_SEC); +} + +static u32 tc358746_us_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz) +{ + return tc358746_cfg_to_cnt(cfg_val, clk_mhz, 1); +} + +static int tc358746_apply_dphy_config(struct tc358746 *tc358746) +{ + struct phy_configure_opts_mipi_dphy *cfg = &tc358746->dphy_cfg; + bool non_cont_clk = !!(tc358746->csi_vep.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK); + struct device *dev = tc358746->sd.dev; + unsigned long hs_byte_clk, hf_clk; + u32 val, val2, lptxcnt; + int err; + + /* The hs_byte_clk is also called SYSCLK in the excel sheet */ + hs_byte_clk = cfg->hs_clk_rate / 8; + hs_byte_clk /= HZ_PER_MHZ; + hf_clk = hs_byte_clk / 2; + + val = tc358746_us_to_cnt(cfg->init, hf_clk) - 1; + dev_dbg(dev, "LINEINITCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, LINEINITCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->lpx, hs_byte_clk) - 1; + lptxcnt = val; + dev_dbg(dev, "LPTXTIMECNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, LPTXTIMECNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_prepare, hs_byte_clk) - 1; + val2 = tc358746_ps_to_cnt(cfg->clk_zero, hs_byte_clk) - 1; + dev_dbg(dev, "TCLK_PREPARECNT: %u (0x%x)\n", val, val); + dev_dbg(dev, "TCLK_ZEROCNT: %u (0x%x)\n", val2, val2); + dev_dbg(dev, "TCLK_HEADERCNT: 0x%x\n", + (u32)(TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2))); + err = tc358746_write(tc358746, TCLK_HEADERCNT_REG, + TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2)); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_trail, hs_byte_clk); + dev_dbg(dev, "TCLK_TRAILCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TCLK_TRAILCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->hs_prepare, hs_byte_clk) - 1; + val2 = tc358746_ps_to_cnt(cfg->hs_zero, hs_byte_clk) - 1; + dev_dbg(dev, "THS_PREPARECNT: %u (0x%x)\n", val, val); + dev_dbg(dev, "THS_ZEROCNT: %u (0x%x)\n", val2, val2); + dev_dbg(dev, "THS_HEADERCNT: 0x%x\n", + (u32)(THS_PREPARECNT(val) | THS_ZEROCNT(val2))); + err = tc358746_write(tc358746, THS_HEADERCNT_REG, + THS_PREPARECNT(val) | THS_ZEROCNT(val2)); + if (err) + return err; + + /* TWAKEUP > 1ms in lptxcnt steps */ + val = tc358746_us_to_cnt(cfg->wakeup, hs_byte_clk); + val = val / (lptxcnt + 1) - 1; + dev_dbg(dev, "TWAKEUP: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TWAKEUP_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_post, hs_byte_clk); + dev_dbg(dev, "TCLK_POSTCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TCLK_POSTCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->hs_trail, hs_byte_clk); + dev_dbg(dev, "THS_TRAILCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, THS_TRAILCNT_REG, val); + if (err) + return err; + + dev_dbg(dev, "CONTCLKMODE: %u", non_cont_clk ? 0 : 1); + + return tc358746_write(tc358746, TXOPTIONCNTRL_REG, non_cont_clk ? 0 : 1); +} + +#define MAX_DATA_LANES 4 + +static int tc358746_enable_csi_lanes(struct tc358746 *tc358746, int enable) +{ + unsigned int lanes = tc358746->dphy_cfg.lanes; + unsigned int lane; + u32 reg, val; + int err; + + err = tc358746_update_bits(tc358746, CONFCTL_REG, DATALANE_MASK, + lanes - 1); + if (err) + return err; + + /* Clock lane */ + val = enable ? 0 : LANEDISABLE; + dev_dbg(tc358746->sd.dev, "CLW_CNTRL: 0x%x\n", val); + err = tc358746_write(tc358746, CLW_CNTRL_REG, val); + if (err) + return err; + + for (lane = 0; lane < MAX_DATA_LANES; lane++) { + /* Data lanes */ + reg = D0W_CNTRL_REG + lane * 0x4; + val = (enable && lane < lanes) ? 0 : LANEDISABLE; + + dev_dbg(tc358746->sd.dev, "D%uW_CNTRL: 0x%x\n", lane, val); + err = tc358746_write(tc358746, reg, val); + if (err) + return err; + } + + val = 0; + if (enable) { + /* Clock lane */ + val |= BIT(0); + + /* Data lanes */ + for (lane = 1; lane <= lanes; lane++) + val |= BIT(lane); + } + + dev_dbg(tc358746->sd.dev, "HSTXVREGEN: 0x%x\n", val); + + return tc358746_write(tc358746, HSTXVREGEN_REG, val); +} + +static int tc358746_enable_csi_module(struct tc358746 *tc358746, int enable) +{ + unsigned int lanes = tc358746->dphy_cfg.lanes; + int err; + + /* + * START and STRT are only reseted/disabled by sw reset. This is + * required to put the lane state back into LP-11 state. The sw reset + * don't reset register values. + */ + if (!enable) + return tc358746_sw_reset(tc358746); + + err = tc358746_write(tc358746, STARTCNTRL_REG, START); + if (err) + return err; + + err = tc358746_write(tc358746, CSI_START_REG, STRT); + if (err) + return err; + + /* CSI_CONTROL_REG is only indirect accessible */ + return tc358746_write(tc358746, CSI_CONFW_REG, + MODE(MODE_SET) | + ADDRESS(CSI_CONTROL_ADDRESS) | + DATA(CSI_MODE | TXHSMD | NOL(lanes - 1))); +} + +static int tc358746_enable_parallel_port(struct tc358746 *tc358746, int enable) +{ + int err; + + if (enable) { + err = tc358746_write(tc358746, PP_MISC_REG, 0); + if (err) + return err; + + return tc358746_set_bits(tc358746, CONFCTL_REG, PPEN); + } + + err = tc358746_set_bits(tc358746, PP_MISC_REG, FRMSTOP); + if (err) + return err; + + err = tc358746_clear_bits(tc358746, CONFCTL_REG, PPEN); + if (err) + return err; + + return tc358746_set_bits(tc358746, PP_MISC_REG, RSTPTR); +} + +static inline struct v4l2_subdev *tc358746_get_remote_sd(struct media_pad *pad) +{ + pad = media_pad_remote_pad_first(pad); + if (!pad) + return NULL; + + return media_entity_to_v4l2_subdev(pad->entity); +} + +static int tc358746_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + struct v4l2_subdev *src; + int err; + + dev_dbg(sd->dev, "%sable\n", enable ? "en" : "dis"); + + src = tc358746_get_remote_sd(&tc358746->pads[TC358746_SINK]); + if (!src) + return -EPIPE; + + if (enable) { + err = pm_runtime_resume_and_get(sd->dev); + if (err) + return err; + + err = tc358746_apply_dphy_config(tc358746); + if (err) + goto err_out; + + err = tc358746_apply_misc_config(tc358746); + if (err) + goto err_out; + + err = tc358746_enable_csi_lanes(tc358746, 1); + if (err) + goto err_out; + + err = tc358746_enable_csi_module(tc358746, 1); + if (err) + goto err_out; + + err = tc358746_enable_parallel_port(tc358746, 1); + if (err) + goto err_out; + + err = v4l2_subdev_call(src, video, s_stream, 1); + if (err) + goto err_out; + + return 0; + +err_out: + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return err; + } + + /* + * The lanes must be disabled first (before the csi module) so the + * LP-11 state is entered correctly. + */ + err = tc358746_enable_csi_lanes(tc358746, 0); + if (err) + return err; + + err = tc358746_enable_csi_module(tc358746, 0); + if (err) + return err; + + err = tc358746_enable_parallel_port(tc358746, 0); + if (err) + return err; + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return v4l2_subdev_call(src, video, s_stream, 0); +} + +static int tc358746_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SINK); + *fmt = tc358746_def_fmt; + + fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SOURCE); + *fmt = tc358746_def_fmt; + fmt->code = tc358746_src_mbus_code(tc358746_def_fmt.code); + + return 0; +} + +static int tc358746_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct tc358746_format *fmt; + + fmt = tc358746_get_format_by_idx(code->pad, code->index); + if (IS_ERR(fmt)) + return PTR_ERR(fmt); + + code->code = fmt->code; + + return 0; +} + +static int tc358746_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *src_fmt, *sink_fmt; + const struct tc358746_format *fmt; + + /* Source follows the sink */ + if (format->pad == TC358746_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, format); + + sink_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SINK); + + fmt = tc358746_get_format_by_code(format->pad, format->format.code); + if (IS_ERR(fmt)) + fmt = tc358746_get_format_by_code(format->pad, tc358746_def_fmt.code); + + format->format.code = fmt->code; + format->format.field = V4L2_FIELD_NONE; + + dev_dbg(sd->dev, "Update format: %ux%u code:0x%x -> %ux%u code:0x%x", + sink_fmt->width, sink_fmt->height, sink_fmt->code, + format->format.width, format->format.height, format->format.code); + + *sink_fmt = format->format; + + src_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SOURCE); + *src_fmt = *sink_fmt; + src_fmt->code = tc358746_src_mbus_code(sink_fmt->code); + + return 0; +} + +static unsigned long tc358746_find_pll_settings(struct tc358746 *tc358746, + unsigned long refclk, + unsigned long fout) + +{ + struct device *dev = tc358746->sd.dev; + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u16 prediv_max = 17; + u16 prediv_min = 1; + u16 m_best, mul; + u16 p_best, p; + u8 postdiv; + + if (fout > 1000 * HZ_PER_MHZ) { + dev_err(dev, "HS-Clock above 1 Ghz are not supported\n"); + return 0; + } + + if (fout >= 500 * HZ_PER_MHZ) + postdiv = 1; + else if (fout >= 250 * HZ_PER_MHZ) + postdiv = 2; + else if (fout >= 125 * HZ_PER_MHZ) + postdiv = 4; + else + postdiv = 8; + + for (p = prediv_min; p <= prediv_max; p++) { + unsigned long delta, fin; + u64 tmp; + + fin = DIV_ROUND_CLOSEST(refclk, p); + if (fin < 4 * HZ_PER_MHZ || fin > 40 * HZ_PER_MHZ) + continue; + + tmp = fout * p * postdiv; + do_div(tmp, fin); + mul = tmp; + if (mul > 511) + continue; + + tmp = mul * fin; + do_div(tmp, p * postdiv); + + delta = abs(fout - tmp); + if (delta < min_delta) { + p_best = p; + m_best = mul; + min_delta = delta; + best_freq = tmp; + }; + + if (delta == 0) + break; + }; + + if (!best_freq) { + dev_err(dev, "Failed find PLL frequency\n"); + return 0; + } + + tc358746->pll_post_div = postdiv; + tc358746->pll_pre_div = p_best; + tc358746->pll_mul = m_best; + + if (best_freq != fout) + dev_warn(dev, "Request PLL freq:%lu, found PLL freq:%lu\n", + fout, best_freq); + + dev_dbg(dev, "Found PLL settings: freq:%lu prediv:%u multi:%u postdiv:%u\n", + best_freq, p_best, m_best, postdiv); + + return best_freq; +} + +#define TC358746_PRECISION 10 + +static int +tc358746_link_validate(struct v4l2_subdev *sd, struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + unsigned long csi_bitrate, source_bitrate; + struct v4l2_subdev_state *sink_state; + struct v4l2_mbus_framefmt *mbusfmt; + const struct tc358746_format *fmt; + unsigned int fifo_sz, tmp, n; + struct v4l2_subdev *source; + s64 source_link_freq; + int err; + + err = v4l2_subdev_link_validate_default(sd, link, source_fmt, sink_fmt); + if (err) + return err; + + sink_state = v4l2_subdev_lock_and_get_active_state(sd); + mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK); + + /* Check the FIFO settings */ + fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code); + + source = media_entity_to_v4l2_subdev(link->source->entity); + source_link_freq = v4l2_get_link_freq(source->ctrl_handler, 0, 0); + if (source_link_freq <= 0) { + dev_err(tc358746->sd.dev, + "Failed to query or invalid source link frequency\n"); + v4l2_subdev_unlock_state(sink_state); + /* Return -EINVAL in case of source_link_freq is 0 */ + return source_link_freq ? : -EINVAL; + } + source_bitrate = source_link_freq * fmt->bus_width; + + csi_bitrate = tc358746->dphy_cfg.lanes * tc358746->pll_rate; + + dev_dbg(tc358746->sd.dev, + "Fifo settings params: source-bitrate:%lu csi-bitrate:%lu", + source_bitrate, csi_bitrate); + + /* Avoid possible FIFO overflows */ + if (csi_bitrate < source_bitrate) { + v4l2_subdev_unlock_state(sink_state); + return -EINVAL; + } + + /* Best case */ + if (csi_bitrate == source_bitrate) { + fifo_sz = TC358746_VB_DEFAULT_SIZE; + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; + goto out; + } + + /* + * Avoid possible FIFO underflow in case of + * csi_bitrate > source_bitrate. For such case the chip has a internal + * fifo which can be used to delay the line output. + * + * Fifo size calculation (excluding precision): + * + * fifo-sz, image-width - in bits + * sbr - source_bitrate in bits/s + * csir - csi_bitrate in bits/s + * + * image-width / csir >= (image-width - fifo-sz) / sbr + * image-width * sbr / csir >= image-width - fifo-sz + * fifo-sz >= image-width - image-width * sbr / csir; with n = csir/sbr + * fifo-sz >= image-width - image-width / n + */ + + source_bitrate /= TC358746_PRECISION; + n = csi_bitrate / source_bitrate; + tmp = (mbusfmt->width * TC358746_PRECISION) / n; + fifo_sz = mbusfmt->width - tmp; + fifo_sz *= fmt->bpp; + tc358746->vb_size = round_up(fifo_sz, 32); + +out: + dev_dbg(tc358746->sd.dev, + "Found FIFO size[bits]:%u -> aligned to size[bits]:%u\n", + fifo_sz, tc358746->vb_size); + + v4l2_subdev_unlock_state(sink_state); + + return tc358746->vb_size > TC358746_VB_MAX_SIZE ? -EINVAL : 0; +} + +static int tc358746_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + if (pad != TC358746_SOURCE) + return -EINVAL; + + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2 = tc358746->csi_vep.bus.mipi_csi2; + + return 0; +} + +static int __maybe_unused +tc358746_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + reg->size = reg->reg < CLW_DPHYCONTTX_REG ? 2 : 4; + + if (!pm_runtime_get_if_in_use(sd->dev)) + return 0; + + tc358746_read(tc358746, reg->reg, (u32 *)®->val); + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return 0; +} + +static int __maybe_unused +tc358746_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + if (!pm_runtime_get_if_in_use(sd->dev)) + return 0; + + tc358746_write(tc358746, (u32)reg->reg, (u32)reg->val); + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_core_ops tc358746_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tc358746_g_register, + .s_register = tc358746_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops tc358746_video_ops = { + .s_stream = tc358746_s_stream, +}; + +static const struct v4l2_subdev_pad_ops tc358746_pad_ops = { + .init_cfg = tc358746_init_cfg, + .enum_mbus_code = tc358746_enum_mbus_code, + .set_fmt = tc358746_set_fmt, + .get_fmt = v4l2_subdev_get_fmt, + .link_validate = tc358746_link_validate, + .get_mbus_config = tc358746_get_mbus_config, +}; + +static const struct v4l2_subdev_ops tc358746_ops = { + .core = &tc358746_core_ops, + .video = &tc358746_video_ops, + .pad = &tc358746_pad_ops, +}; + +static const struct media_entity_operations tc358746_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate = v4l2_subdev_link_validate, +}; + +static int tc358746_mclk_enable(struct clk_hw *hw) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + unsigned int div; + u32 val; + int err; + + div = tc358746->mclk_postdiv / 2; + val = MCLK_HIGH(div - 1) | MCLK_LOW(div - 1); + dev_dbg(tc358746->sd.dev, "MCLKCTL: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, MCLKCTL_REG, val); + if (err) + return err; + + if (tc358746->mclk_prediv == 8) + val = MCLKDIV(MCLKDIV_8); + else if (tc358746->mclk_prediv == 4) + val = MCLKDIV(MCLKDIV_4); + else + val = MCLKDIV(MCLKDIV_2); + + dev_dbg(tc358746->sd.dev, "CLKCTL[MCLKDIV]: %u (0x%x)\n", val, val); + + return tc358746_update_bits(tc358746, CLKCTL_REG, MCLKDIV_MASK, val); +} + +static void tc358746_mclk_disable(struct clk_hw *hw) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + tc358746_write(tc358746, MCLKCTL_REG, 0); +} + +static long +tc358746_find_mclk_settings(struct tc358746 *tc358746, unsigned long mclk_rate) +{ + unsigned long pll_rate = tc358746->pll_rate; + const unsigned char prediv[] = { 2, 4, 8 }; + unsigned int mclk_prediv, mclk_postdiv; + struct device *dev = tc358746->sd.dev; + unsigned int postdiv, mclkdiv; + unsigned long best_mclk_rate; + unsigned int i; + + /* + * MCLK-Div + * -------------------´`--------------------- + * ´ ` + * +-------------+ +------------------------+ + * | MCLK-PreDiv | | MCLK-PostDiv | + * PLL --> | (2/4/8) | --> | (mclk_low + mclk_high) | --> MCLK + * +-------------+ +------------------------+ + * + * The register value of mclk_low/high is mclk_low/high+1, i.e.: + * mclk_low/high = 1 --> 2 MCLK-Ref Counts + * mclk_low/high = 255 --> 256 MCLK-Ref Counts == max. + * If mclk_low and mclk_high are 0 then MCLK is disabled. + * + * Keep it simple and support 50/50 duty cycles only for now, + * so the calc will be: + * + * MCLK = PLL / (MCLK-PreDiv * 2 * MCLK-PostDiv) + */ + + if (mclk_rate == tc358746->mclk_rate) + return mclk_rate; + + /* Highest possible rate */ + mclkdiv = pll_rate / mclk_rate; + if (mclkdiv <= 8) { + mclk_prediv = 2; + mclk_postdiv = 4; + best_mclk_rate = pll_rate / (2 * 4); + goto out; + } + + /* First check the prediv */ + for (i = 0; i < ARRAY_SIZE(prediv); i++) { + postdiv = mclkdiv / prediv[i]; + + if (postdiv % 2) + continue; + + if (postdiv >= 4 && postdiv <= 512) { + mclk_prediv = prediv[i]; + mclk_postdiv = postdiv; + best_mclk_rate = pll_rate / (prediv[i] * postdiv); + goto out; + } + } + + /* No suitable prediv found, so try to adjust the postdiv */ + for (postdiv = 4; postdiv <= 512; postdiv += 2) { + unsigned int pre; + + pre = mclkdiv / postdiv; + if (pre == 2 || pre == 4 || pre == 8) { + mclk_prediv = pre; + mclk_postdiv = postdiv; + best_mclk_rate = pll_rate / (pre * postdiv); + goto out; + } + } + + /* The MCLK <-> PLL gap is to high -> use largest possible div */ + mclk_prediv = 8; + mclk_postdiv = 512; + best_mclk_rate = pll_rate / (8 * 512); + +out: + tc358746->mclk_prediv = mclk_prediv; + tc358746->mclk_postdiv = mclk_postdiv; + tc358746->mclk_rate = best_mclk_rate; + + if (best_mclk_rate != mclk_rate) + dev_warn(dev, "Request MCLK freq:%lu, found MCLK freq:%lu\n", + mclk_rate, best_mclk_rate); + + dev_dbg(dev, "Found MCLK settings: freq:%lu prediv:%u postdiv:%u\n", + best_mclk_rate, mclk_prediv, mclk_postdiv); + + return best_mclk_rate; +} + +static unsigned long +tc358746_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + unsigned int prediv, postdiv; + u32 val; + int err; + + err = tc358746_read(tc358746, MCLKCTL_REG, &val); + if (err) + return 0; + + postdiv = FIELD_GET(MCLK_LOW_MASK, val) + 1; + postdiv += FIELD_GET(MCLK_HIGH_MASK, val) + 1; + + err = tc358746_read(tc358746, CLKCTL_REG, &val); + if (err) + return 0; + + prediv = FIELD_GET(MCLKDIV_MASK, val); + if (prediv == MCLKDIV_8) + prediv = 8; + else if (prediv == MCLKDIV_4) + prediv = 4; + else + prediv = 2; + + return tc358746->pll_rate / (prediv * postdiv); +} + +static long tc358746_mclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + *parent_rate = tc358746->pll_rate; + + return tc358746_find_mclk_settings(tc358746, rate); +} + +static int tc358746_mclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + tc358746_find_mclk_settings(tc358746, rate); + + return tc358746_mclk_enable(hw); +} + +static const struct clk_ops tc358746_mclk_ops = { + .enable = tc358746_mclk_enable, + .disable = tc358746_mclk_disable, + .recalc_rate = tc358746_recalc_rate, + .round_rate = tc358746_mclk_round_rate, + .set_rate = tc358746_mclk_set_rate, +}; + +static int tc358746_setup_mclk_provider(struct tc358746 *tc358746) +{ + struct clk_init_data mclk_initdata = { }; + struct device *dev = tc358746->sd.dev; + const char *mclk_name; + int err; + + /* MCLK clk provider support is optional */ + if (!device_property_present(dev, "#clock-cells")) + return 0; + + /* Init to highest possibel MCLK */ + tc358746->mclk_postdiv = 512; + tc358746->mclk_prediv = 8; + + mclk_name = "tc358746-mclk"; + device_property_read_string(dev, "clock-output-names", &mclk_name); + + mclk_initdata.name = mclk_name; + mclk_initdata.ops = &tc358746_mclk_ops; + tc358746->mclk_hw.init = &mclk_initdata; + + err = devm_clk_hw_register(dev, &tc358746->mclk_hw); + if (err) { + dev_err(dev, "Failed to register mclk provider\n"); + return err; + } + + err = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &tc358746->mclk_hw); + if (err) + dev_err(dev, "Failed to add mclk provider\n"); + + return err; +} + +static int +tc358746_init_subdev(struct tc358746 *tc358746, struct i2c_client *client) +{ + struct v4l2_subdev *sd = &tc358746->sd; + int err; + + v4l2_i2c_subdev_init(sd, client, &tc358746_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &tc358746_entity_ops; + + tc358746->pads[TC358746_SINK].flags = MEDIA_PAD_FL_SINK; + tc358746->pads[TC358746_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + err = media_entity_pads_init(&sd->entity, TC358746_NR_PADS, + tc358746->pads); + if (err) + return err; + + err = v4l2_subdev_init_finalize(sd); + if (err) + media_entity_cleanup(&sd->entity); + + return err; +} + +static int +tc358746_init_output_port(struct tc358746 *tc358746, unsigned long refclk) +{ + struct device *dev = tc358746->sd.dev; + struct v4l2_fwnode_endpoint *vep; + unsigned long csi_link_rate; + struct fwnode_handle *ep; + unsigned char csi_lanes; + int err; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), TC358746_SOURCE, + 0, 0); + if (!ep) { + dev_err(dev, "Missing endpoint node\n"); + return -EINVAL; + } + + /* Currently we only support 'parallel in' -> 'csi out' */ + vep = &tc358746->csi_vep; + vep->bus_type = V4L2_MBUS_CSI2_DPHY; + err = v4l2_fwnode_endpoint_alloc_parse(ep, vep); + fwnode_handle_put(ep); + if (err) { + dev_err(dev, "Failed to parse source endpoint\n"); + return err; + } + + csi_lanes = vep->bus.mipi_csi2.num_data_lanes; + if (csi_lanes == 0 || csi_lanes > 4 || + vep->nr_of_link_frequencies == 0) { + dev_err(dev, "error: Invalid CSI-2 settings\n"); + err = -EINVAL; + goto err; + } + + /* TODO: Add support to handle multiple link frequencies */ + csi_link_rate = (unsigned long)vep->link_frequencies[0]; + tc358746->pll_rate = tc358746_find_pll_settings(tc358746, refclk, + csi_link_rate * 2); + if (!tc358746->pll_rate) { + err = -EINVAL; + goto err; + } + + err = phy_mipi_dphy_get_default_config_for_hsclk(tc358746->pll_rate, + csi_lanes, &tc358746->dphy_cfg); + if (err) + goto err; + + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; + + return 0; + +err: + v4l2_fwnode_endpoint_free(vep); + + return err; +} + +static int tc358746_init_hw(struct tc358746 *tc358746) +{ + struct device *dev = tc358746->sd.dev; + unsigned int chipid; + u32 val; + int err; + + err = pm_runtime_resume_and_get(dev); + if (err < 0) { + dev_err(dev, "Failed to resume the device\n"); + return err; + } + + /* Ensure that CSI interface is put into LP-11 state */ + err = tc358746_sw_reset(tc358746); + if (err) { + pm_runtime_put_sync(dev); + dev_err(dev, "Failed to reset the device\n"); + return err; + } + + err = tc358746_read(tc358746, CHIPID_REG, &val); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_sync_autosuspend(dev); + if (err) + return -ENODEV; + + chipid = FIELD_GET(CHIPID, val); + if (chipid != 0x44) { + dev_err(dev, "Invalid chipid 0x%02x\n", chipid); + return -ENODEV; + } + + return 0; +} + +static int tc358746_init_controls(struct tc358746 *tc358746) +{ + u64 *link_frequencies = tc358746->csi_vep.link_frequencies; + struct v4l2_ctrl *ctrl; + int err; + + err = v4l2_ctrl_handler_init(&tc358746->ctrl_hdl, 1); + if (err) + return err; + + /* + * The driver currently supports only one link-frequency, regardless of + * the input from the firmware, see: tc358746_init_output_port(). So + * report only the first frequency from the array of possible given + * frequencies. + */ + ctrl = v4l2_ctrl_new_int_menu(&tc358746->ctrl_hdl, NULL, + V4L2_CID_LINK_FREQ, 0, 0, + link_frequencies); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + err = tc358746->ctrl_hdl.error; + if (err) { + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); + return err; + } + + tc358746->sd.ctrl_handler = &tc358746->ctrl_hdl; + + return 0; +} + +static int tc358746_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct tc358746 *tc358746 = + container_of(notifier, struct tc358746, notifier); + u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE; + struct media_pad *sink = &tc358746->pads[TC358746_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, flags); +} + +static const struct v4l2_async_notifier_operations tc358746_notify_ops = { + .bound = tc358746_notify_bound, +}; + +static int tc358746_async_register(struct tc358746 *tc358746) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_PARALLEL, + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + int err; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(tc358746->sd.dev), + TC358746_SINK, 0, 0); + if (!ep) + return -ENOTCONN; + + err = v4l2_fwnode_endpoint_parse(ep, &vep); + if (err) { + fwnode_handle_put(ep); + return err; + } + + v4l2_async_nf_init(&tc358746->notifier); + asd = v4l2_async_nf_add_fwnode_remote(&tc358746->notifier, ep, + struct v4l2_async_subdev); + fwnode_handle_put(ep); + + if (IS_ERR(asd)) { + err = PTR_ERR(asd); + goto err_cleanup; + } + + tc358746->notifier.ops = &tc358746_notify_ops; + + err = v4l2_async_subdev_nf_register(&tc358746->sd, &tc358746->notifier); + if (err) + goto err_cleanup; + + tc358746->sd.fwnode = fwnode_graph_get_endpoint_by_id( + dev_fwnode(tc358746->sd.dev), TC358746_SOURCE, 0, 0); + + err = v4l2_async_register_subdev(&tc358746->sd); + if (err) + goto err_unregister; + + return 0; + +err_unregister: + fwnode_handle_put(tc358746->sd.fwnode); + v4l2_async_nf_unregister(&tc358746->notifier); +err_cleanup: + v4l2_async_nf_cleanup(&tc358746->notifier); + + return err; +} + +static int tc358746_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tc358746 *tc358746; + unsigned long refclk; + unsigned int i; + int err; + + tc358746 = devm_kzalloc(&client->dev, sizeof(*tc358746), GFP_KERNEL); + if (!tc358746) + return -ENOMEM; + + tc358746->regmap = devm_regmap_init_i2c(client, &tc358746_regmap_config); + if (IS_ERR(tc358746->regmap)) + return dev_err_probe(dev, PTR_ERR(tc358746->regmap), + "Failed to init regmap\n"); + + tc358746->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(tc358746->refclk)) + return dev_err_probe(dev, PTR_ERR(tc358746->refclk), + "Failed to get refclk\n"); + + err = clk_prepare_enable(tc358746->refclk); + if (err) + return dev_err_probe(dev, err, + "Failed to enable refclk\n"); + + refclk = clk_get_rate(tc358746->refclk); + clk_disable_unprepare(tc358746->refclk); + + if (refclk < 6 * HZ_PER_MHZ || refclk > 40 * HZ_PER_MHZ) + return dev_err_probe(dev, -EINVAL, "Invalid refclk range\n"); + + for (i = 0; i < ARRAY_SIZE(tc358746_supplies); i++) + tc358746->supplies[i].supply = tc358746_supplies[i]; + + err = devm_regulator_bulk_get(dev, ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + if (err) + return dev_err_probe(dev, err, "Failed to get supplies\n"); + + tc358746->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(tc358746->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(tc358746->reset_gpio), + "Failed to get reset-gpios\n"); + + err = tc358746_init_subdev(tc358746, client); + if (err) + return dev_err_probe(dev, err, "Failed to init subdev\n"); + + err = tc358746_init_output_port(tc358746, refclk); + if (err) + goto err_subdev; + + /* + * Keep this order since we need the output port link-frequencies + * information. + */ + err = tc358746_init_controls(tc358746); + if (err) + goto err_fwnode; + + dev_set_drvdata(dev, tc358746); + + /* Set to 1sec to give the stream reconfiguration enough time */ + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + err = tc358746_init_hw(tc358746); + if (err) + goto err_pm; + + err = tc358746_setup_mclk_provider(tc358746); + if (err) + goto err_pm; + + err = tc358746_async_register(tc358746); + if (err < 0) + goto err_pm; + + dev_dbg(dev, "%s found @ 0x%x (%s)\n", client->name, + client->addr, client->adapter->name); + + return 0; + +err_pm: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); +err_fwnode: + v4l2_fwnode_endpoint_free(&tc358746->csi_vep); +err_subdev: + v4l2_subdev_cleanup(&tc358746->sd); + media_entity_cleanup(&tc358746->sd.entity); + + return err; +} + +static void tc358746_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tc358746 *tc358746 = to_tc358746(sd); + + v4l2_subdev_cleanup(sd); + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); + v4l2_fwnode_endpoint_free(&tc358746->csi_vep); + v4l2_async_nf_unregister(&tc358746->notifier); + v4l2_async_nf_cleanup(&tc358746->notifier); + fwnode_handle_put(sd->fwnode); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + pm_runtime_disable(sd->dev); + pm_runtime_set_suspended(sd->dev); + pm_runtime_dont_use_autosuspend(sd->dev); +} + +static int tc358746_suspend(struct device *dev) +{ + struct tc358746 *tc358746 = dev_get_drvdata(dev); + int err; + + clk_disable_unprepare(tc358746->refclk); + + err = regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + if (err) + clk_prepare_enable(tc358746->refclk); + + return err; +} + +static int tc358746_resume(struct device *dev) +{ + struct tc358746 *tc358746 = dev_get_drvdata(dev); + int err; + + gpiod_set_value(tc358746->reset_gpio, 1); + + err = regulator_bulk_enable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + if (err) + return err; + + /* min. 200ns */ + usleep_range(10, 20); + + gpiod_set_value(tc358746->reset_gpio, 0); + + err = clk_prepare_enable(tc358746->refclk); + if (err) + goto err; + + /* min. 700us ... 1ms */ + usleep_range(1000, 1500); + + /* + * Enable the PLL here since it can be called by the clk-framework or by + * the .s_stream() callback. So this is the common place for both. + */ + err = tc358746_apply_pll_config(tc358746); + if (err) + goto err_clk; + + return 0; + +err_clk: + clk_disable_unprepare(tc358746->refclk); +err: + regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + return err; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tc358746_pm_ops, tc358746_suspend, + tc358746_resume, NULL); + +static const struct of_device_id __maybe_unused tc358746_of_match[] = { + { .compatible = "toshiba,tc358746" }, + { }, +}; +MODULE_DEVICE_TABLE(of, tc358746_of_match); + +static struct i2c_driver tc358746_driver = { + .driver = { + .name = "tc358746", + .pm = pm_ptr(&tc358746_pm_ops), + .of_match_table = tc358746_of_match, + }, + .probe_new = tc358746_probe, + .remove = tc358746_remove, +}; + +module_i2c_driver(tc358746_driver); + +MODULE_DESCRIPTION("Toshiba TC358746 Parallel to CSI-2 bridge driver"); +MODULE_AUTHOR("Marco Felsch <kernel@pengutronix.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/tda7432.c b/drivers/media/i2c/tda7432.c index 11e918311b13..bbceaac8e0b3 100644 --- a/drivers/media/i2c/tda7432.c +++ b/drivers/media/i2c/tda7432.c @@ -343,8 +343,7 @@ static const struct v4l2_subdev_ops tda7432_ops = { * i2c interface functions * * *********************** */ -static int tda7432_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda7432_probe(struct i2c_client *client) { struct tda7432 *t; struct v4l2_subdev *sd; @@ -410,7 +409,7 @@ static struct i2c_driver tda7432_driver = { .driver = { .name = "tda7432", }, - .probe = tda7432_probe, + .probe_new = tda7432_probe, .remove = tda7432_remove, .id_table = tda7432_id, }; diff --git a/drivers/media/i2c/tda9840.c b/drivers/media/i2c/tda9840.c index aaa74944fc7c..25fbd7e3950e 100644 --- a/drivers/media/i2c/tda9840.c +++ b/drivers/media/i2c/tda9840.c @@ -149,8 +149,7 @@ static const struct v4l2_subdev_ops tda9840_ops = { /* ----------------------------------------------------------------------- */ -static int tda9840_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda9840_probe(struct i2c_client *client) { struct v4l2_subdev *sd; @@ -192,7 +191,7 @@ static struct i2c_driver tda9840_driver = { .driver = { .name = "tda9840", }, - .probe = tda9840_probe, + .probe_new = tda9840_probe, .remove = tda9840_remove, .id_table = tda9840_id, }; diff --git a/drivers/media/i2c/tea6415c.c b/drivers/media/i2c/tea6415c.c index 50e74314f315..d375d2d24354 100644 --- a/drivers/media/i2c/tea6415c.c +++ b/drivers/media/i2c/tea6415c.c @@ -116,8 +116,7 @@ static const struct v4l2_subdev_ops tea6415c_ops = { .video = &tea6415c_video_ops, }; -static int tea6415c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tea6415c_probe(struct i2c_client *client) { struct v4l2_subdev *sd; @@ -151,7 +150,7 @@ static struct i2c_driver tea6415c_driver = { .driver = { .name = "tea6415c", }, - .probe = tea6415c_probe, + .probe_new = tea6415c_probe, .remove = tea6415c_remove, .id_table = tea6415c_id, }; diff --git a/drivers/media/i2c/tea6420.c b/drivers/media/i2c/tea6420.c index 246f2b10ccc7..9da1f3b02c57 100644 --- a/drivers/media/i2c/tea6420.c +++ b/drivers/media/i2c/tea6420.c @@ -87,8 +87,7 @@ static const struct v4l2_subdev_ops tea6420_ops = { .audio = &tea6420_audio_ops, }; -static int tea6420_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tea6420_probe(struct i2c_client *client) { struct v4l2_subdev *sd; int err, i; @@ -133,7 +132,7 @@ static struct i2c_driver tea6420_driver = { .driver = { .name = "tea6420", }, - .probe = tea6420_probe, + .probe_new = tea6420_probe, .remove = tea6420_remove, .id_table = tea6420_id, }; diff --git a/drivers/media/i2c/ths7303.c b/drivers/media/i2c/ths7303.c index 2a0f9a3d1a66..67de90cf696e 100644 --- a/drivers/media/i2c/ths7303.c +++ b/drivers/media/i2c/ths7303.c @@ -322,8 +322,7 @@ static const struct v4l2_subdev_ops ths7303_ops = { .video = &ths7303_video_ops, }; -static int ths7303_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ths7303_probe(struct i2c_client *client) { struct ths7303_platform_data *pdata = client->dev.platform_data; struct ths7303_state *state; @@ -377,7 +376,7 @@ static struct i2c_driver ths7303_driver = { .driver = { .name = "ths73x3", }, - .probe = ths7303_probe, + .probe_new = ths7303_probe, .remove = ths7303_remove, .id_table = ths7303_id, }; diff --git a/drivers/media/i2c/tlv320aic23b.c b/drivers/media/i2c/tlv320aic23b.c index 937fa1dbaecb..47198e803817 100644 --- a/drivers/media/i2c/tlv320aic23b.c +++ b/drivers/media/i2c/tlv320aic23b.c @@ -129,8 +129,7 @@ static const struct v4l2_subdev_ops tlv320aic23b_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int tlv320aic23b_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tlv320aic23b_probe(struct i2c_client *client) { struct tlv320aic23b_state *state; struct v4l2_subdev *sd; @@ -198,7 +197,7 @@ static struct i2c_driver tlv320aic23b_driver = { .driver = { .name = "tlv320aic23b", }, - .probe = tlv320aic23b_probe, + .probe_new = tlv320aic23b_probe, .remove = tlv320aic23b_remove, .id_table = tlv320aic23b_id, }; diff --git a/drivers/media/i2c/tw2804.c b/drivers/media/i2c/tw2804.c index c7c8dfe8a8a8..710790ece11b 100644 --- a/drivers/media/i2c/tw2804.c +++ b/drivers/media/i2c/tw2804.c @@ -343,8 +343,7 @@ static const struct v4l2_subdev_ops tw2804_ops = { .video = &tw2804_video_ops, }; -static int tw2804_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tw2804_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct tw2804 *state; @@ -424,7 +423,7 @@ static struct i2c_driver tw2804_driver = { .driver = { .name = "tw2804", }, - .probe = tw2804_probe, + .probe_new = tw2804_probe, .remove = tw2804_remove, .id_table = tw2804_id, }; diff --git a/drivers/media/i2c/tw9903.c b/drivers/media/i2c/tw9903.c index d7eef7986b75..428ee55787e1 100644 --- a/drivers/media/i2c/tw9903.c +++ b/drivers/media/i2c/tw9903.c @@ -189,8 +189,7 @@ static const struct v4l2_subdev_ops tw9903_ops = { /* --------------------------------------------------------------------------*/ -static int tw9903_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tw9903_probe(struct i2c_client *client) { struct tw9903 *dec; struct v4l2_subdev *sd; @@ -255,7 +254,7 @@ static struct i2c_driver tw9903_driver = { .driver = { .name = "tw9903", }, - .probe = tw9903_probe, + .probe_new = tw9903_probe, .remove = tw9903_remove, .id_table = tw9903_id, }; diff --git a/drivers/media/i2c/tw9906.c b/drivers/media/i2c/tw9906.c index 549ad8f72f12..7824ed9b04ed 100644 --- a/drivers/media/i2c/tw9906.c +++ b/drivers/media/i2c/tw9906.c @@ -157,8 +157,7 @@ static const struct v4l2_subdev_ops tw9906_ops = { .video = &tw9906_video_ops, }; -static int tw9906_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tw9906_probe(struct i2c_client *client) { struct tw9906 *dec; struct v4l2_subdev *sd; @@ -223,7 +222,7 @@ static struct i2c_driver tw9906_driver = { .driver = { .name = "tw9906", }, - .probe = tw9906_probe, + .probe_new = tw9906_probe, .remove = tw9906_remove, .id_table = tw9906_id, }; diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c index 853b5acead32..459fa22f4341 100644 --- a/drivers/media/i2c/tw9910.c +++ b/drivers/media/i2c/tw9910.c @@ -928,8 +928,7 @@ static const struct v4l2_subdev_ops tw9910_subdev_ops = { * i2c_driver function */ -static int tw9910_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int tw9910_probe(struct i2c_client *client) { struct tw9910_priv *priv; @@ -1013,7 +1012,7 @@ static struct i2c_driver tw9910_i2c_driver = { .driver = { .name = "tw9910", }, - .probe = tw9910_probe, + .probe_new = tw9910_probe, .remove = tw9910_remove, .id_table = tw9910_id, }; diff --git a/drivers/media/i2c/uda1342.c b/drivers/media/i2c/uda1342.c index d0659c4392f2..b6873d866272 100644 --- a/drivers/media/i2c/uda1342.c +++ b/drivers/media/i2c/uda1342.c @@ -45,8 +45,7 @@ static const struct v4l2_subdev_ops uda1342_ops = { .audio = &uda1342_audio_ops, }; -static int uda1342_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int uda1342_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct v4l2_subdev *sd; @@ -89,7 +88,7 @@ static struct i2c_driver uda1342_driver = { .driver = { .name = "uda1342", }, - .probe = uda1342_probe, + .probe_new = uda1342_probe, .remove = uda1342_remove, .id_table = uda1342_id, }; diff --git a/drivers/media/i2c/upd64031a.c b/drivers/media/i2c/upd64031a.c index 4de26ed2ba00..47eed3aab060 100644 --- a/drivers/media/i2c/upd64031a.c +++ b/drivers/media/i2c/upd64031a.c @@ -183,8 +183,7 @@ static const struct v4l2_subdev_ops upd64031a_ops = { /* i2c implementation */ -static int upd64031a_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int upd64031a_probe(struct i2c_client *client) { struct upd64031a_state *state; struct v4l2_subdev *sd; @@ -229,7 +228,7 @@ static struct i2c_driver upd64031a_driver = { .driver = { .name = "upd64031a", }, - .probe = upd64031a_probe, + .probe_new = upd64031a_probe, .remove = upd64031a_remove, .id_table = upd64031a_id, }; diff --git a/drivers/media/i2c/upd64083.c b/drivers/media/i2c/upd64083.c index 2bfd5443d406..3f5a7d4853a1 100644 --- a/drivers/media/i2c/upd64083.c +++ b/drivers/media/i2c/upd64083.c @@ -154,8 +154,7 @@ static const struct v4l2_subdev_ops upd64083_ops = { /* i2c implementation */ -static int upd64083_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int upd64083_probe(struct i2c_client *client) { struct upd64083_state *state; struct v4l2_subdev *sd; @@ -200,7 +199,7 @@ static struct i2c_driver upd64083_driver = { .driver = { .name = "upd64083", }, - .probe = upd64083_probe, + .probe_new = upd64083_probe, .remove = upd64083_remove, .id_table = upd64083_id, }; diff --git a/drivers/media/i2c/vp27smpx.c b/drivers/media/i2c/vp27smpx.c index c832edad5fa7..ed1c58ea8ed3 100644 --- a/drivers/media/i2c/vp27smpx.c +++ b/drivers/media/i2c/vp27smpx.c @@ -138,8 +138,7 @@ static const struct v4l2_subdev_ops vp27smpx_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int vp27smpx_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vp27smpx_probe(struct i2c_client *client) { struct vp27smpx_state *state; struct v4l2_subdev *sd; @@ -182,7 +181,7 @@ static struct i2c_driver vp27smpx_driver = { .driver = { .name = "vp27smpx", }, - .probe = vp27smpx_probe, + .probe_new = vp27smpx_probe, .remove = vp27smpx_remove, .id_table = vp27smpx_id, }; diff --git a/drivers/media/i2c/vpx3220.c b/drivers/media/i2c/vpx3220.c index b481ec196b88..aa73d5dcc3e7 100644 --- a/drivers/media/i2c/vpx3220.c +++ b/drivers/media/i2c/vpx3220.c @@ -456,8 +456,7 @@ static const struct v4l2_subdev_ops vpx3220_ops = { * Client management code */ -static int vpx3220_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vpx3220_probe(struct i2c_client *client) { struct vpx3220 *decoder; struct v4l2_subdev *sd; @@ -547,7 +546,7 @@ static struct i2c_driver vpx3220_driver = { .driver = { .name = "vpx3220", }, - .probe = vpx3220_probe, + .probe_new = vpx3220_probe, .remove = vpx3220_remove, .id_table = vpx3220_id, }; diff --git a/drivers/media/i2c/vs6624.c b/drivers/media/i2c/vs6624.c index d496bb45f201..d35c5ec148f4 100644 --- a/drivers/media/i2c/vs6624.c +++ b/drivers/media/i2c/vs6624.c @@ -738,8 +738,7 @@ static const struct v4l2_subdev_ops vs6624_ops = { .pad = &vs6624_pad_ops, }; -static int vs6624_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vs6624_probe(struct i2c_client *client) { struct vs6624 *sensor; struct v4l2_subdev *sd; @@ -843,7 +842,7 @@ static struct i2c_driver vs6624_driver = { .driver = { .name = "vs6624", }, - .probe = vs6624_probe, + .probe_new = vs6624_probe, .remove = vs6624_remove, .id_table = vs6624_id, }; diff --git a/drivers/media/i2c/wm8739.c b/drivers/media/i2c/wm8739.c index 180b35347521..8b34a673ffd3 100644 --- a/drivers/media/i2c/wm8739.c +++ b/drivers/media/i2c/wm8739.c @@ -178,8 +178,7 @@ static const struct v4l2_subdev_ops wm8739_ops = { /* i2c implementation */ -static int wm8739_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int wm8739_probe(struct i2c_client *client) { struct wm8739_state *state; struct v4l2_subdev *sd; @@ -253,7 +252,7 @@ static struct i2c_driver wm8739_driver = { .driver = { .name = "wm8739", }, - .probe = wm8739_probe, + .probe_new = wm8739_probe, .remove = wm8739_remove, .id_table = wm8739_id, }; diff --git a/drivers/media/i2c/wm8775.c b/drivers/media/i2c/wm8775.c index 8ff97867d3cd..56d98518f7eb 100644 --- a/drivers/media/i2c/wm8775.c +++ b/drivers/media/i2c/wm8775.c @@ -190,8 +190,7 @@ static const struct v4l2_subdev_ops wm8775_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int wm8775_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int wm8775_probe(struct i2c_client *client) { struct wm8775_state *state; struct v4l2_subdev *sd; @@ -299,7 +298,7 @@ static struct i2c_driver wm8775_driver = { .driver = { .name = "wm8775", }, - .probe = wm8775_probe, + .probe_new = wm8775_probe, .remove = wm8775_remove, .id_table = wm8775_id, }; diff --git a/drivers/media/pci/bt8xx/bttv.h b/drivers/media/pci/bt8xx/bttv.h index d24b9ef9f59f..eed7eeb3b963 100644 --- a/drivers/media/pci/bt8xx/bttv.h +++ b/drivers/media/pci/bt8xx/bttv.h @@ -289,7 +289,6 @@ extern void bttv_init_card2(struct bttv *btv); extern void bttv_init_tuner(struct bttv *btv); /* card-specific functions */ -extern void tea5757_set_freq(struct bttv *btv, unsigned short freq); extern u32 bttv_tda9880_setnorm(struct bttv *btv, u32 gpiobits); /* extra tweaks for some chipsets */ diff --git a/drivers/media/pci/cx25821/cx25821-video.h b/drivers/media/pci/cx25821/cx25821-video.h index cf0d3f5509e7..080c8490a250 100644 --- a/drivers/media/pci/cx25821/cx25821-video.h +++ b/drivers/media/pci/cx25821/cx25821-video.h @@ -36,9 +36,6 @@ do { \ } while (0) #define FORMAT_FLAGS_PACKED 0x01 -extern void cx25821_video_wakeup(struct cx25821_dev *dev, - struct cx25821_dmaqueue *q, u32 count); - extern int cx25821_start_video_dma(struct cx25821_dev *dev, struct cx25821_dmaqueue *q, struct cx25821_buffer *buf, diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig index 39bd3be0b43d..65b0c1598fbf 100644 --- a/drivers/media/pci/intel/ipu3/Kconfig +++ b/drivers/media/pci/intel/ipu3/Kconfig @@ -21,6 +21,7 @@ config VIDEO_IPU3_CIO2 config CIO2_BRIDGE bool "IPU3 CIO2 Sensors Bridge" depends on VIDEO_IPU3_CIO2 && ACPI + depends on I2C help This extension provides an API for the ipu3-cio2 driver to create connections to cameras that are hidden in the SSDB buffer in ACPI. diff --git a/drivers/media/pci/mantis/Kconfig b/drivers/media/pci/mantis/Kconfig index 9dfaf2c9a7b3..374b8f382f62 100644 --- a/drivers/media/pci/mantis/Kconfig +++ b/drivers/media/pci/mantis/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config MANTIS_CORE tristate "Mantis/Hopper PCI bridge based devices" - depends on PCI && I2C && INPUT && RC_CORE + depends on PCI && I2C && INPUT && RC_CORE && DVB_CORE help Support for PCI cards based on the Mantis and Hopper PCi bridge. diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c index f6deac85962e..246f73b8a9e7 100644 --- a/drivers/media/pci/pt3/pt3.c +++ b/drivers/media/pci/pt3/pt3.c @@ -707,18 +707,10 @@ static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (ret < 0) return ret; - ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)); - if (ret == 0) - dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); - else { - ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); - if (ret == 0) - dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); - else { - dev_err(&pdev->dev, "Failed to set DMA mask\n"); - return ret; - } - dev_info(&pdev->dev, "Use 32bit DMA\n"); + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + dev_err(&pdev->dev, "Failed to set DMA mask\n"); + return ret; } pt3 = devm_kzalloc(&pdev->dev, sizeof(*pt3), GFP_KERNEL); diff --git a/drivers/media/pci/saa7134/saa7134.h b/drivers/media/pci/saa7134/saa7134.h index 49fe0f6bacba..5c9b2912a9d1 100644 --- a/drivers/media/pci/saa7134/saa7134.h +++ b/drivers/media/pci/saa7134/saa7134.h @@ -866,7 +866,6 @@ int saa7134_ts_stop(struct saa7134_dev *dev); /* saa7134-vbi.c */ extern const struct vb2_ops saa7134_vbi_qops; -extern struct video_device saa7134_vbi_template; int saa7134_vbi_init1(struct saa7134_dev *dev); int saa7134_vbi_fini(struct saa7134_dev *dev); @@ -897,9 +896,6 @@ void saa7134_enable_i2s(struct saa7134_dev *dev); /* ----------------------------------------------------------- */ /* saa7134-oss.c */ -extern const struct file_operations saa7134_dsp_fops; -extern const struct file_operations saa7134_mixer_fops; - int saa7134_oss_init1(struct saa7134_dev *dev); int saa7134_oss_fini(struct saa7134_dev *dev); void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status); diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c index d5f32e3ff544..a8a004f28ca0 100644 --- a/drivers/media/pci/saa7164/saa7164-core.c +++ b/drivers/media/pci/saa7164/saa7164-core.c @@ -352,7 +352,7 @@ static void saa7164_work_enchandler(struct work_struct *w) container_of(w, struct saa7164_port, workenc); struct saa7164_dev *dev = port->dev; - u32 wp, mcb, rp, cnt = 0; + u32 wp, mcb, rp; port->last_svc_msecs_diff = port->last_svc_msecs; port->last_svc_msecs = jiffies_to_msecs(jiffies); @@ -405,7 +405,6 @@ static void saa7164_work_enchandler(struct work_struct *w) saa7164_work_enchandler_helper(port, rp); port->last_svc_rp = rp; - cnt++; if (rp == mcb) break; @@ -429,7 +428,7 @@ static void saa7164_work_vbihandler(struct work_struct *w) container_of(w, struct saa7164_port, workenc); struct saa7164_dev *dev = port->dev; - u32 wp, mcb, rp, cnt = 0; + u32 wp, mcb, rp; port->last_svc_msecs_diff = port->last_svc_msecs; port->last_svc_msecs = jiffies_to_msecs(jiffies); @@ -481,7 +480,6 @@ static void saa7164_work_vbihandler(struct work_struct *w) saa7164_work_enchandler_helper(port, rp); port->last_svc_rp = rp; - cnt++; if (rp == mcb) break; @@ -1259,7 +1257,7 @@ static int saa7164_initdev(struct pci_dev *pci_dev, if (saa7164_dev_setup(dev) < 0) { err = -EINVAL; - goto fail_free; + goto fail_dev; } /* print pci info */ @@ -1427,6 +1425,8 @@ fail_fw: fail_irq: saa7164_dev_unregister(dev); +fail_dev: + pci_disable_device(pci_dev); fail_free: v4l2_device_unregister(&dev->v4l2_dev); kfree(dev); diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h index 4b4eb156e214..eede47b686a3 100644 --- a/drivers/media/pci/saa7164/saa7164.h +++ b/drivers/media/pci/saa7164/saa7164.h @@ -493,8 +493,6 @@ int saa7164_downloadfirmware(struct saa7164_dev *dev); /* saa7164-i2c.c */ extern int saa7164_i2c_register(struct saa7164_i2c *bus); extern int saa7164_i2c_unregister(struct saa7164_i2c *bus); -extern void saa7164_call_i2c_clients(struct saa7164_i2c *bus, - unsigned int cmd, void *arg); /* ----------------------------------------------------------- */ /* saa7164-bus.c */ diff --git a/drivers/media/pci/solo6x10/solo6x10-core.c b/drivers/media/pci/solo6x10/solo6x10-core.c index 4a546eeefe38..6d87fbb0ee04 100644 --- a/drivers/media/pci/solo6x10/solo6x10-core.c +++ b/drivers/media/pci/solo6x10/solo6x10-core.c @@ -420,6 +420,7 @@ static int solo_sysfs_init(struct solo_dev *solo_dev) solo_dev->nr_chans); if (device_register(dev)) { + put_device(dev); dev->parent = NULL; return -ENOMEM; } diff --git a/drivers/media/pci/zoran/zoran_device.h b/drivers/media/pci/zoran/zoran_device.h index 34fd5cc914eb..237e830ae726 100644 --- a/drivers/media/pci/zoran/zoran_device.h +++ b/drivers/media/pci/zoran/zoran_device.h @@ -47,8 +47,6 @@ void zr36057_restart(struct zoran *zr); extern const struct zoran_format zoran_formats[]; -extern int v4l_bufsize; -extern int jpg_bufsize; extern int pass_through; /* i2c */ diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index a9334263fa9b..ee579916f874 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -72,6 +72,7 @@ source "drivers/media/platform/chips-media/Kconfig" source "drivers/media/platform/intel/Kconfig" source "drivers/media/platform/marvell/Kconfig" source "drivers/media/platform/mediatek/Kconfig" +source "drivers/media/platform/microchip/Kconfig" source "drivers/media/platform/nvidia/Kconfig" source "drivers/media/platform/nxp/Kconfig" source "drivers/media/platform/qcom/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index a91f42024273..5453bb868e67 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -15,6 +15,7 @@ obj-y += chips-media/ obj-y += intel/ obj-y += marvell/ obj-y += mediatek/ +obj-y += microchip/ obj-y += nvidia/ obj-y += nxp/ obj-y += qcom/ diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c index feb75dc204de..87f9f8e90ab1 100644 --- a/drivers/media/platform/amphion/vdec.c +++ b/drivers/media/platform/amphion/vdec.c @@ -69,72 +69,101 @@ struct vdec_t { static const struct vpu_format vdec_formats[] = { { .pixfmt = V4L2_PIX_FMT_NV12M_8L128, - .num_planes = 2, + .mem_planes = 2, + .comp_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12_8L128, + }, + { + .pixfmt = V4L2_PIX_FMT_NV12_8L128, + .mem_planes = 1, + .comp_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12M_8L128, }, { .pixfmt = V4L2_PIX_FMT_NV12M_10BE_8L128, - .num_planes = 2, + .mem_planes = 2, + .comp_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12_10BE_8L128, + }, + { + .pixfmt = V4L2_PIX_FMT_NV12_10BE_8L128, + .mem_planes = 1, + .comp_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12M_10BE_8L128 }, { .pixfmt = V4L2_PIX_FMT_H264, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_H264_MVC, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_HEVC, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_G, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_L, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_MPEG2, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_MPEG4, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_XVID, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_VP8, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_H263, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, {0, 0, 0, 0}, }; @@ -256,23 +285,22 @@ static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) int ret = -EINVAL; vpu_inst_lock(inst); - if (!V4L2_TYPE_IS_OUTPUT(f->type) && vdec->fixed_fmt) { - if (f->index == 0) { - f->pixelformat = inst->cap_format.pixfmt; - f->flags = inst->cap_format.flags; - ret = 0; - } + if (V4L2_TYPE_IS_CAPTURE(f->type) && vdec->fixed_fmt) { + fmt = vpu_get_format(inst, f->type); + if (f->index == 1) + fmt = vpu_helper_find_sibling(inst, f->type, fmt->pixfmt); + if (f->index > 1) + fmt = NULL; } else { fmt = vpu_helper_enum_format(inst, f->type, f->index); - memset(f->reserved, 0, sizeof(f->reserved)); - if (!fmt) - goto exit; - - f->pixelformat = fmt->pixfmt; - f->flags = fmt->flags; - ret = 0; } + if (!fmt) + goto exit; + memset(f->reserved, 0, sizeof(f->reserved)); + f->pixelformat = fmt->pixfmt; + f->flags = fmt->flags; + ret = 0; exit: vpu_inst_unlock(inst); return ret; @@ -286,23 +314,25 @@ static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f) struct vpu_format *cur_fmt; int i; + vpu_inst_lock(inst); cur_fmt = vpu_get_format(inst, f->type); pixmp->pixelformat = cur_fmt->pixfmt; - pixmp->num_planes = cur_fmt->num_planes; + pixmp->num_planes = cur_fmt->mem_planes; pixmp->width = cur_fmt->width; pixmp->height = cur_fmt->height; pixmp->field = cur_fmt->field; pixmp->flags = cur_fmt->flags; for (i = 0; i < pixmp->num_planes; i++) { pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; - pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(cur_fmt, i); } f->fmt.pix_mp.colorspace = vdec->codec_info.color_primaries; f->fmt.pix_mp.xfer_func = vdec->codec_info.transfer_chars; f->fmt.pix_mp.ycbcr_enc = vdec->codec_info.matrix_coeffs; f->fmt.pix_mp.quantization = vdec->codec_info.full_range; + vpu_inst_unlock(inst); return 0; } @@ -311,10 +341,19 @@ static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct vpu_inst *inst = to_inst(file); struct vdec_t *vdec = inst->priv; - - vpu_try_fmt_common(inst, f); + struct vpu_format fmt; vpu_inst_lock(inst); + if (V4L2_TYPE_IS_CAPTURE(f->type) && vdec->fixed_fmt) { + struct vpu_format *cap_fmt = vpu_get_format(inst, f->type); + + if (!vpu_helper_match_format(inst, cap_fmt->type, cap_fmt->pixfmt, + f->fmt.pix_mp.pixelformat)) + f->fmt.pix_mp.pixelformat = cap_fmt->pixfmt; + } + + vpu_try_fmt_common(inst, f, &fmt); + if (vdec->fixed_fmt) { f->fmt.pix_mp.colorspace = vdec->codec_info.color_primaries; f->fmt.pix_mp.xfer_func = vdec->codec_info.transfer_chars; @@ -334,7 +373,7 @@ static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) static int vdec_s_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) { struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; - const struct vpu_format *fmt; + struct vpu_format fmt; struct vpu_format *cur_fmt; struct vb2_queue *q; struct vdec_t *vdec = inst->priv; @@ -349,36 +388,30 @@ static int vdec_s_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) if (vb2_is_busy(q)) return -EBUSY; - fmt = vpu_try_fmt_common(inst, f); - if (!fmt) + if (vpu_try_fmt_common(inst, f, &fmt)) return -EINVAL; cur_fmt = vpu_get_format(inst, f->type); if (V4L2_TYPE_IS_OUTPUT(f->type) && inst->state != VPU_CODEC_STATE_DEINIT) { - if (cur_fmt->pixfmt != fmt->pixfmt) { + if (cur_fmt->pixfmt != fmt.pixfmt) { vdec->reset_codec = true; vdec->fixed_fmt = false; } } - cur_fmt->pixfmt = fmt->pixfmt; if (V4L2_TYPE_IS_OUTPUT(f->type) || !vdec->fixed_fmt) { - cur_fmt->num_planes = fmt->num_planes; - cur_fmt->flags = fmt->flags; - cur_fmt->width = pixmp->width; - cur_fmt->height = pixmp->height; - for (i = 0; i < fmt->num_planes; i++) { - cur_fmt->sizeimage[i] = pixmp->plane_fmt[i].sizeimage; - cur_fmt->bytesperline[i] = pixmp->plane_fmt[i].bytesperline; - } - if (pixmp->field != V4L2_FIELD_ANY) - cur_fmt->field = pixmp->field; + memcpy(cur_fmt, &fmt, sizeof(*cur_fmt)); } else { - pixmp->num_planes = cur_fmt->num_planes; + if (vpu_helper_match_format(inst, f->type, cur_fmt->pixfmt, pixmp->pixelformat)) { + cur_fmt->pixfmt = fmt.pixfmt; + cur_fmt->mem_planes = fmt.mem_planes; + } + pixmp->pixelformat = cur_fmt->pixfmt; + pixmp->num_planes = cur_fmt->mem_planes; pixmp->width = cur_fmt->width; pixmp->height = cur_fmt->height; for (i = 0; i < pixmp->num_planes; i++) { pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; - pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(cur_fmt, i); } pixmp->field = cur_fmt->field; } @@ -678,9 +711,11 @@ static struct vpu_vb2_buffer *vdec_find_buffer(struct vpu_inst *inst, u32 luma) static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) { struct vdec_t *vdec = inst->priv; + struct vpu_format *cur_fmt; struct vpu_vb2_buffer *vpu_buf; struct vb2_v4l2_buffer *vbuf; u32 sequence; + int i; if (!frame) return; @@ -699,6 +734,7 @@ static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) return; } + cur_fmt = vpu_get_format(inst, inst->cap_format.type); vbuf = &vpu_buf->m2m_buf.vb; if (vbuf->vb2_buf.index != frame->id) dev_err(inst->dev, "[%d] buffer id(%d, %d) dismatch\n", @@ -707,9 +743,9 @@ static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) if (vpu_get_buffer_state(vbuf) != VPU_BUF_STATE_DECODED) dev_err(inst->dev, "[%d] buffer(%d) ready without decoded\n", inst->id, frame->id); vpu_set_buffer_state(vbuf, VPU_BUF_STATE_READY); - vb2_set_plane_payload(&vbuf->vb2_buf, 0, inst->cap_format.sizeimage[0]); - vb2_set_plane_payload(&vbuf->vb2_buf, 1, inst->cap_format.sizeimage[1]); - vbuf->field = inst->cap_format.field; + for (i = 0; i < vbuf->vb2_buf.num_planes; i++) + vb2_set_plane_payload(&vbuf->vb2_buf, i, vpu_get_fmt_plane_size(cur_fmt, i)); + vbuf->field = cur_fmt->field; vbuf->sequence = sequence; dev_dbg(inst->dev, "[%d][OUTPUT TS]%32lld\n", inst->id, vbuf->vb2_buf.timestamp); @@ -747,15 +783,20 @@ static void vdec_stop_done(struct vpu_inst *inst) static bool vdec_check_source_change(struct vpu_inst *inst) { struct vdec_t *vdec = inst->priv; - const struct vpu_format *fmt; - int i; + const struct vpu_format *sibling; if (!inst->fh.m2m_ctx) return false; + if (vdec->reset_codec) + return false; + + sibling = vpu_helper_find_sibling(inst, inst->cap_format.type, inst->cap_format.pixfmt); + if (sibling && vdec->codec_info.pixfmt == sibling->pixfmt) + vdec->codec_info.pixfmt = inst->cap_format.pixfmt; + if (!vb2_is_streaming(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx))) return true; - fmt = vpu_helper_find_format(inst, inst->cap_format.type, vdec->codec_info.pixfmt); if (inst->cap_format.pixfmt != vdec->codec_info.pixfmt) return true; if (inst->cap_format.width != vdec->codec_info.decoded_width) @@ -772,14 +813,6 @@ static bool vdec_check_source_change(struct vpu_inst *inst) return true; if (inst->crop.height != vdec->codec_info.height) return true; - if (fmt && inst->cap_format.num_planes != fmt->num_planes) - return true; - for (i = 0; i < inst->cap_format.num_planes; i++) { - if (inst->cap_format.bytesperline[i] != vdec->codec_info.bytesperline[i]) - return true; - if (inst->cap_format.sizeimage[i] != vdec->codec_info.sizeimage[i]) - return true; - } return false; } @@ -787,27 +820,21 @@ static bool vdec_check_source_change(struct vpu_inst *inst) static void vdec_init_fmt(struct vpu_inst *inst) { struct vdec_t *vdec = inst->priv; - const struct vpu_format *fmt; - int i; + struct v4l2_format f; - fmt = vpu_helper_find_format(inst, inst->cap_format.type, vdec->codec_info.pixfmt); - inst->out_format.width = vdec->codec_info.width; - inst->out_format.height = vdec->codec_info.height; - inst->cap_format.width = vdec->codec_info.decoded_width; - inst->cap_format.height = vdec->codec_info.decoded_height; - inst->cap_format.pixfmt = vdec->codec_info.pixfmt; - if (fmt) { - inst->cap_format.num_planes = fmt->num_planes; - inst->cap_format.flags = fmt->flags; - } - for (i = 0; i < inst->cap_format.num_planes; i++) { - inst->cap_format.bytesperline[i] = vdec->codec_info.bytesperline[i]; - inst->cap_format.sizeimage[i] = vdec->codec_info.sizeimage[i]; - } + memset(&f, 0, sizeof(f)); + f.type = inst->cap_format.type; + f.fmt.pix_mp.pixelformat = vdec->codec_info.pixfmt; + f.fmt.pix_mp.width = vdec->codec_info.decoded_width; + f.fmt.pix_mp.height = vdec->codec_info.decoded_height; if (vdec->codec_info.progressive) - inst->cap_format.field = V4L2_FIELD_NONE; + f.fmt.pix_mp.field = V4L2_FIELD_NONE; else - inst->cap_format.field = V4L2_FIELD_SEQ_TB; + f.fmt.pix_mp.field = V4L2_FIELD_SEQ_TB; + vpu_try_fmt_common(inst, &f, &inst->cap_format); + + inst->out_format.width = vdec->codec_info.width; + inst->out_format.height = vdec->codec_info.height; } static void vdec_init_crop(struct vpu_inst *inst) @@ -966,7 +993,10 @@ static int vdec_response_frame(struct vpu_inst *inst, struct vb2_v4l2_buffer *vb info.tag = vdec->seq_tag; info.luma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 0); info.luma_size = inst->cap_format.sizeimage[0]; - info.chroma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 1); + if (vbuf->vb2_buf.num_planes > 1) + info.chroma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 1); + else + info.chroma_addr = info.luma_addr + info.luma_size; info.chromau_size = inst->cap_format.sizeimage[1]; info.bytesperline = inst->cap_format.bytesperline[0]; ret = vpu_session_alloc_fs(inst, &info); @@ -975,7 +1005,7 @@ static int vdec_response_frame(struct vpu_inst *inst, struct vb2_v4l2_buffer *vb vpu_buf->tag = info.tag; vpu_buf->luma = info.luma_addr; - vpu_buf->chroma_u = info.chromau_size; + vpu_buf->chroma_u = info.chroma_addr; vpu_buf->chroma_v = 0; vpu_set_buffer_state(vbuf, VPU_BUF_STATE_INUSE); vdec->slots[info.id] = vpu_buf; @@ -1088,7 +1118,8 @@ static void vdec_event_seq_hdr(struct vpu_inst *inst, struct vpu_dec_codec_info vdec->seq_tag = vdec->codec_info.tag; if (vdec->is_source_changed) { vdec_update_state(inst, VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE, 0); - vpu_notify_source_change(inst); + vdec->source_change++; + vdec_handle_resolution_change(inst); vdec->is_source_changed = false; } } @@ -1335,6 +1366,8 @@ static void vdec_abort(struct vpu_inst *inst) vdec->decoded_frame_count, vdec->display_frame_count, vdec->sequence); + if (!vdec->seq_hdr_found) + vdec->reset_codec = true; vdec->params.end_flag = 0; vdec->drain = 0; vdec->params.frame_count = 0; @@ -1342,6 +1375,7 @@ static void vdec_abort(struct vpu_inst *inst) vdec->display_frame_count = 0; vdec->sequence = 0; vdec->aborting = false; + inst->extra_size = 0; } static void vdec_stop(struct vpu_inst *inst, bool free) @@ -1464,8 +1498,7 @@ static int vdec_start_session(struct vpu_inst *inst, u32 type) } if (V4L2_TYPE_IS_OUTPUT(type)) { - if (inst->state == VPU_CODEC_STATE_SEEK) - vdec_update_state(inst, vdec->state, 1); + vdec_update_state(inst, vdec->state, 1); vdec->eos_received = 0; vpu_process_output_buffer(inst); } else { @@ -1629,6 +1662,7 @@ static int vdec_open(struct file *file) return ret; vdec->fixed_fmt = false; + vdec->state = VPU_CODEC_STATE_ACTIVE; inst->min_buffer_cap = VDEC_MIN_BUFFER_CAP; inst->min_buffer_out = VDEC_MIN_BUFFER_OUT; vdec_init(file); diff --git a/drivers/media/platform/amphion/venc.c b/drivers/media/platform/amphion/venc.c index 37212f087fdd..3cbe8ce637e5 100644 --- a/drivers/media/platform/amphion/venc.c +++ b/drivers/media/platform/amphion/venc.c @@ -69,13 +69,24 @@ struct venc_frame_t { static const struct vpu_format venc_formats[] = { { .pixfmt = V4L2_PIX_FMT_NV12M, - .num_planes = 2, + .mem_planes = 2, + .comp_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .sibling = V4L2_PIX_FMT_NV12, + }, + { + .pixfmt = V4L2_PIX_FMT_NV12, + .mem_planes = 1, + .comp_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .sibling = V4L2_PIX_FMT_NV12M, }, { .pixfmt = V4L2_PIX_FMT_H264, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .flags = V4L2_FMT_FLAG_COMPRESSED }, {0, 0, 0, 0}, }; @@ -173,14 +184,14 @@ static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) cur_fmt = vpu_get_format(inst, f->type); pixmp->pixelformat = cur_fmt->pixfmt; - pixmp->num_planes = cur_fmt->num_planes; + pixmp->num_planes = cur_fmt->mem_planes; pixmp->width = cur_fmt->width; pixmp->height = cur_fmt->height; pixmp->field = cur_fmt->field; pixmp->flags = cur_fmt->flags; for (i = 0; i < pixmp->num_planes; i++) { pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; - pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(cur_fmt, i); } f->fmt.pix_mp.colorspace = venc->params.color.primaries; @@ -194,8 +205,9 @@ static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct vpu_inst *inst = to_inst(file); + struct vpu_format fmt; - vpu_try_fmt_common(inst, f); + vpu_try_fmt_common(inst, f, &fmt); return 0; } @@ -203,12 +215,11 @@ static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f) static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct vpu_inst *inst = to_inst(file); - const struct vpu_format *fmt; + struct vpu_format fmt; struct vpu_format *cur_fmt; struct vb2_queue *q; struct venc_t *venc = inst->priv; struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; - int i; q = v4l2_m2m_get_vq(inst->fh.m2m_ctx, f->type); if (!q) @@ -216,24 +227,12 @@ static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) if (vb2_is_busy(q)) return -EBUSY; - fmt = vpu_try_fmt_common(inst, f); - if (!fmt) + if (vpu_try_fmt_common(inst, f, &fmt)) return -EINVAL; cur_fmt = vpu_get_format(inst, f->type); - cur_fmt->pixfmt = fmt->pixfmt; - cur_fmt->num_planes = fmt->num_planes; - cur_fmt->flags = fmt->flags; - cur_fmt->width = pix_mp->width; - cur_fmt->height = pix_mp->height; - for (i = 0; i < fmt->num_planes; i++) { - cur_fmt->sizeimage[i] = pix_mp->plane_fmt[i].sizeimage; - cur_fmt->bytesperline[i] = pix_mp->plane_fmt[i].bytesperline; - } - - if (pix_mp->field != V4L2_FIELD_ANY) - cur_fmt->field = pix_mp->field; + memcpy(cur_fmt, &fmt, sizeof(*cur_fmt)); if (V4L2_TYPE_IS_OUTPUT(f->type)) { venc->params.input_format = cur_fmt->pixfmt; diff --git a/drivers/media/platform/amphion/vpu.h b/drivers/media/platform/amphion/vpu.h index beac0309ca8d..3bfe193722af 100644 --- a/drivers/media/platform/amphion/vpu.h +++ b/drivers/media/platform/amphion/vpu.h @@ -13,6 +13,7 @@ #include <linux/mailbox_controller.h> #include <linux/kfifo.h> +#define VPU_TIMEOUT_WAKEUP msecs_to_jiffies(200) #define VPU_TIMEOUT msecs_to_jiffies(1000) #define VPU_INST_NULL_ID (-1L) #define VPU_MSG_BUFFER_SIZE (8192) @@ -84,7 +85,8 @@ struct vpu_dev { struct vpu_format { u32 pixfmt; - unsigned int num_planes; + u32 mem_planes; + u32 comp_planes; u32 type; u32 flags; u32 width; @@ -92,6 +94,7 @@ struct vpu_format { u32 sizeimage[VIDEO_MAX_PLANES]; u32 bytesperline[VIDEO_MAX_PLANES]; u32 field; + u32 sibling; }; struct vpu_core_resources { diff --git a/drivers/media/platform/amphion/vpu_cmds.c b/drivers/media/platform/amphion/vpu_cmds.c index f4d7ca78a621..fa581ba6bab2 100644 --- a/drivers/media/platform/amphion/vpu_cmds.c +++ b/drivers/media/platform/amphion/vpu_cmds.c @@ -269,7 +269,7 @@ exit: return flag; } -static int sync_session_response(struct vpu_inst *inst, unsigned long key) +static int sync_session_response(struct vpu_inst *inst, unsigned long key, long timeout, int try) { struct vpu_core *core; @@ -279,10 +279,12 @@ static int sync_session_response(struct vpu_inst *inst, unsigned long key) core = inst->core; call_void_vop(inst, wait_prepare); - wait_event_timeout(core->ack_wq, check_is_responsed(inst, key), VPU_TIMEOUT); + wait_event_timeout(core->ack_wq, check_is_responsed(inst, key), timeout); call_void_vop(inst, wait_finish); if (!check_is_responsed(inst, key)) { + if (try) + return -EINVAL; dev_err(inst->dev, "[%d] sync session timeout\n", inst->id); set_bit(inst->id, &core->hang_mask); mutex_lock(&inst->core->cmd_lock); @@ -294,6 +296,19 @@ static int sync_session_response(struct vpu_inst *inst, unsigned long key) return 0; } +static void vpu_core_keep_active(struct vpu_core *core) +{ + struct vpu_rpc_event pkt; + + memset(&pkt, 0, sizeof(pkt)); + vpu_iface_pack_cmd(core, &pkt, 0, VPU_CMD_ID_NOOP, NULL); + + dev_dbg(core->dev, "try to wake up\n"); + mutex_lock(&core->cmd_lock); + vpu_cmd_send(core, &pkt); + mutex_unlock(&core->cmd_lock); +} + static int vpu_session_send_cmd(struct vpu_inst *inst, u32 id, void *data) { unsigned long key; @@ -304,9 +319,25 @@ static int vpu_session_send_cmd(struct vpu_inst *inst, u32 id, void *data) return -EINVAL; ret = vpu_request_cmd(inst, id, data, &key, &sync); - if (!ret && sync) - ret = sync_session_response(inst, key); + if (ret) + goto exit; + + /* workaround for a firmware issue, + * firmware should be waked up by start or configure command, + * but there is a very small change that firmware failed to wakeup. + * in such case, try to wakeup firmware again by sending a noop command + */ + if (sync && (id == VPU_CMD_ID_CONFIGURE_CODEC || id == VPU_CMD_ID_START)) { + if (sync_session_response(inst, key, VPU_TIMEOUT_WAKEUP, 1)) + vpu_core_keep_active(inst->core); + else + goto exit; + } + + if (sync) + ret = sync_session_response(inst, key, VPU_TIMEOUT, 0); +exit: if (ret) dev_err(inst->dev, "[%d] send cmd(0x%x) fail\n", inst->id, id); diff --git a/drivers/media/platform/amphion/vpu_dbg.c b/drivers/media/platform/amphion/vpu_dbg.c index 260f1c4b8f8d..44b830ae01d8 100644 --- a/drivers/media/platform/amphion/vpu_dbg.c +++ b/drivers/media/platform/amphion/vpu_dbg.c @@ -90,9 +90,9 @@ static int vpu_dbg_instance(struct seq_file *s, void *data) vq->last_buffer_dequeued); if (seq_write(s, str, num)) return 0; - for (i = 0; i < inst->out_format.num_planes; i++) { + for (i = 0; i < inst->out_format.mem_planes; i++) { num = scnprintf(str, sizeof(str), " %d(%d)", - inst->out_format.sizeimage[i], + vpu_get_fmt_plane_size(&inst->out_format, i), inst->out_format.bytesperline[i]); if (seq_write(s, str, num)) return 0; @@ -114,9 +114,9 @@ static int vpu_dbg_instance(struct seq_file *s, void *data) vq->last_buffer_dequeued); if (seq_write(s, str, num)) return 0; - for (i = 0; i < inst->cap_format.num_planes; i++) { + for (i = 0; i < inst->cap_format.mem_planes; i++) { num = scnprintf(str, sizeof(str), " %d(%d)", - inst->cap_format.sizeimage[i], + vpu_get_fmt_plane_size(&inst->cap_format, i), inst->cap_format.bytesperline[i]); if (seq_write(s, str, num)) return 0; diff --git a/drivers/media/platform/amphion/vpu_drv.c b/drivers/media/platform/amphion/vpu_drv.c index 9d5a5075343d..f01ce49d27e8 100644 --- a/drivers/media/platform/amphion/vpu_drv.c +++ b/drivers/media/platform/amphion/vpu_drv.c @@ -245,7 +245,11 @@ static int __init vpu_driver_init(void) if (ret) return ret; - return vpu_core_driver_init(); + ret = vpu_core_driver_init(); + if (ret) + platform_driver_unregister(&hion_vpu_driver); + + return ret; } static void __exit vpu_driver_exit(void) diff --git a/drivers/media/platform/amphion/vpu_helpers.c b/drivers/media/platform/amphion/vpu_helpers.c index e9aeb3453dfc..019c77e84514 100644 --- a/drivers/media/platform/amphion/vpu_helpers.c +++ b/drivers/media/platform/amphion/vpu_helpers.c @@ -59,6 +59,36 @@ const struct vpu_format *vpu_helper_find_format(struct vpu_inst *inst, u32 type, return NULL; } +const struct vpu_format *vpu_helper_find_sibling(struct vpu_inst *inst, u32 type, u32 pixelfmt) +{ + const struct vpu_format *fmt; + const struct vpu_format *sibling; + + fmt = vpu_helper_find_format(inst, type, pixelfmt); + if (!fmt || !fmt->sibling) + return NULL; + + sibling = vpu_helper_find_format(inst, type, fmt->sibling); + if (!sibling || sibling->sibling != fmt->pixfmt || + sibling->comp_planes != fmt->comp_planes) + return NULL; + + return sibling; +} + +bool vpu_helper_match_format(struct vpu_inst *inst, u32 type, u32 fmta, u32 fmtb) +{ + const struct vpu_format *sibling; + + if (fmta == fmtb) + return true; + + sibling = vpu_helper_find_sibling(inst, type, fmta); + if (sibling && sibling->pixfmt == fmtb) + return true; + return false; +} + const struct vpu_format *vpu_helper_enum_format(struct vpu_inst *inst, u32 type, int index) { const struct vpu_format *pfmt; @@ -123,9 +153,10 @@ static u32 get_nv12_plane_size(u32 width, u32 height, int plane_no, u32 bytesperline; u32 size = 0; - bytesperline = ALIGN(width, stride); + bytesperline = width; if (pbl) bytesperline = max(bytesperline, *pbl); + bytesperline = ALIGN(bytesperline, stride); height = ALIGN(height, 2); if (plane_no == 0) size = bytesperline * height; @@ -148,13 +179,13 @@ static u32 get_tiled_8l128_plane_size(u32 fmt, u32 width, u32 height, int plane_ if (interlaced) hs++; - if (fmt == V4L2_PIX_FMT_NV12M_10BE_8L128) + if (fmt == V4L2_PIX_FMT_NV12M_10BE_8L128 || fmt == V4L2_PIX_FMT_NV12_10BE_8L128) bitdepth = 10; bytesperline = DIV_ROUND_UP(width * bitdepth, BITS_PER_BYTE); - bytesperline = ALIGN(bytesperline, 1 << ws); - bytesperline = ALIGN(bytesperline, stride); if (pbl) bytesperline = max(bytesperline, *pbl); + bytesperline = ALIGN(bytesperline, 1 << ws); + bytesperline = ALIGN(bytesperline, stride); height = ALIGN(height, 1 << hs); if (plane_no == 0) size = bytesperline * height; @@ -172,9 +203,10 @@ static u32 get_default_plane_size(u32 width, u32 height, int plane_no, u32 bytesperline; u32 size = 0; - bytesperline = ALIGN(width, stride); + bytesperline = width; if (pbl) bytesperline = max(bytesperline, *pbl); + bytesperline = ALIGN(bytesperline, stride); if (plane_no == 0) size = bytesperline * height; if (pbl) @@ -187,9 +219,12 @@ u32 vpu_helper_get_plane_size(u32 fmt, u32 w, u32 h, int plane_no, u32 stride, u32 interlaced, u32 *pbl) { switch (fmt) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: return get_nv12_plane_size(w, h, plane_no, stride, interlaced, pbl); + case V4L2_PIX_FMT_NV12_8L128: case V4L2_PIX_FMT_NV12M_8L128: + case V4L2_PIX_FMT_NV12_10BE_8L128: case V4L2_PIX_FMT_NV12M_10BE_8L128: return get_tiled_8l128_plane_size(fmt, w, h, plane_no, stride, interlaced, pbl); default: diff --git a/drivers/media/platform/amphion/vpu_helpers.h b/drivers/media/platform/amphion/vpu_helpers.h index bc28350958be..0eaddb07190d 100644 --- a/drivers/media/platform/amphion/vpu_helpers.h +++ b/drivers/media/platform/amphion/vpu_helpers.h @@ -14,6 +14,8 @@ struct vpu_pair { int vpu_helper_find_in_array_u8(const u8 *array, u32 size, u32 x); bool vpu_helper_check_type(struct vpu_inst *inst, u32 type); const struct vpu_format *vpu_helper_find_format(struct vpu_inst *inst, u32 type, u32 pixelfmt); +const struct vpu_format *vpu_helper_find_sibling(struct vpu_inst *inst, u32 type, u32 pixelfmt); +bool vpu_helper_match_format(struct vpu_inst *inst, u32 type, u32 fmta, u32 fmtb); const struct vpu_format *vpu_helper_enum_format(struct vpu_inst *inst, u32 type, int index); u32 vpu_helper_valid_frame_width(struct vpu_inst *inst, u32 width); u32 vpu_helper_valid_frame_height(struct vpu_inst *inst, u32 height); diff --git a/drivers/media/platform/amphion/vpu_malone.c b/drivers/media/platform/amphion/vpu_malone.c index 51e0702f9ae1..2c9bfc6a5a72 100644 --- a/drivers/media/platform/amphion/vpu_malone.c +++ b/drivers/media/platform/amphion/vpu_malone.c @@ -583,7 +583,8 @@ bool vpu_malone_check_fmt(enum vpu_core_type type, u32 pixelfmt) if (!vpu_imx8q_check_fmt(type, pixelfmt)) return false; - if (pixelfmt == V4L2_PIX_FMT_NV12M_8L128 || pixelfmt == V4L2_PIX_FMT_NV12M_10BE_8L128) + if (pixelfmt == V4L2_PIX_FMT_NV12_8L128 || pixelfmt == V4L2_PIX_FMT_NV12_10BE_8L128 || + pixelfmt == V4L2_PIX_FMT_NV12M_8L128 || pixelfmt == V4L2_PIX_FMT_NV12M_10BE_8L128) return true; if (vpu_malone_format_remap(pixelfmt) == MALONE_FMT_NULL) return false; @@ -692,6 +693,7 @@ int vpu_malone_set_decode_params(struct vpu_shared_addr *shared, } static struct vpu_pair malone_cmds[] = { + {VPU_CMD_ID_NOOP, VID_API_CMD_NULL}, {VPU_CMD_ID_START, VID_API_CMD_START}, {VPU_CMD_ID_STOP, VID_API_CMD_STOP}, {VPU_CMD_ID_ABORT, VID_API_CMD_ABORT}, diff --git a/drivers/media/platform/amphion/vpu_msgs.c b/drivers/media/platform/amphion/vpu_msgs.c index d8247f36d84b..92672a802b49 100644 --- a/drivers/media/platform/amphion/vpu_msgs.c +++ b/drivers/media/platform/amphion/vpu_msgs.c @@ -43,6 +43,7 @@ static void vpu_session_handle_mem_request(struct vpu_inst *inst, struct vpu_rpc req_data.ref_frame_num, req_data.act_buf_size, req_data.act_buf_num); + vpu_inst_lock(inst); call_void_vop(inst, mem_request, req_data.enc_frame_size, req_data.enc_frame_num, @@ -50,6 +51,7 @@ static void vpu_session_handle_mem_request(struct vpu_inst *inst, struct vpu_rpc req_data.ref_frame_num, req_data.act_buf_size, req_data.act_buf_num); + vpu_inst_unlock(inst); } static void vpu_session_handle_stop_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt) diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c index b779e0ba916c..6773b885597c 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.c +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -65,18 +65,11 @@ unsigned int vpu_get_buffer_state(struct vb2_v4l2_buffer *vbuf) void vpu_v4l2_set_error(struct vpu_inst *inst) { - struct vb2_queue *src_q; - struct vb2_queue *dst_q; - vpu_inst_lock(inst); dev_err(inst->dev, "some error occurs in codec\n"); if (inst->fh.m2m_ctx) { - src_q = v4l2_m2m_get_src_vq(inst->fh.m2m_ctx); - dst_q = v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx); - src_q->error = 1; - dst_q->error = 1; - wake_up(&src_q->done_wq); - wake_up(&dst_q->done_wq); + vb2_queue_error(v4l2_m2m_get_src_vq(inst->fh.m2m_ctx)); + vb2_queue_error(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx)); } vpu_inst_unlock(inst); } @@ -140,51 +133,136 @@ bool vpu_is_source_empty(struct vpu_inst *inst) return true; } -const struct vpu_format *vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) +static int vpu_init_format(struct vpu_inst *inst, struct vpu_format *fmt) +{ + const struct vpu_format *info; + + info = vpu_helper_find_format(inst, fmt->type, fmt->pixfmt); + if (!info) { + info = vpu_helper_enum_format(inst, fmt->type, 0); + if (!info) + return -EINVAL; + } + memcpy(fmt, info, sizeof(*fmt)); + + return 0; +} + +static int vpu_calc_fmt_bytesperline(struct v4l2_format *f, struct vpu_format *fmt) { struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; - u32 type = f->type; + int i; + + if (fmt->flags & V4L2_FMT_FLAG_COMPRESSED) { + for (i = 0; i < fmt->comp_planes; i++) + fmt->bytesperline[i] = 0; + return 0; + } + if (pixmp->num_planes == fmt->comp_planes) { + for (i = 0; i < fmt->comp_planes; i++) + fmt->bytesperline[i] = pixmp->plane_fmt[i].bytesperline; + return 0; + } + if (pixmp->num_planes > 1) + return -EINVAL; + + /*amphion vpu only support nv12 and nv12 tiled, + * so the bytesperline of luma and chroma should be same + */ + for (i = 0; i < fmt->comp_planes; i++) + fmt->bytesperline[i] = pixmp->plane_fmt[0].bytesperline; + + return 0; +} + +static int vpu_calc_fmt_sizeimage(struct vpu_inst *inst, struct vpu_format *fmt) +{ u32 stride = 1; - u32 bytesperline; - u32 sizeimage; - const struct vpu_format *fmt; - const struct vpu_core_resources *res; int i; - fmt = vpu_helper_find_format(inst, type, pixmp->pixelformat); - if (!fmt) { - fmt = vpu_helper_enum_format(inst, type, 0); - if (!fmt) - return NULL; - pixmp->pixelformat = fmt->pixfmt; + if (!(fmt->flags & V4L2_FMT_FLAG_COMPRESSED)) { + const struct vpu_core_resources *res = vpu_get_resource(inst); + + if (res) + stride = res->stride; } - res = vpu_get_resource(inst); - if (res) - stride = res->stride; - if (pixmp->width) - pixmp->width = vpu_helper_valid_frame_width(inst, pixmp->width); - if (pixmp->height) - pixmp->height = vpu_helper_valid_frame_height(inst, pixmp->height); + for (i = 0; i < fmt->comp_planes; i++) { + fmt->sizeimage[i] = vpu_helper_get_plane_size(fmt->pixfmt, + fmt->width, + fmt->height, + i, + stride, + fmt->field != V4L2_FIELD_NONE ? 1 : 0, + &fmt->bytesperline[i]); + fmt->sizeimage[i] = max_t(u32, fmt->sizeimage[i], PAGE_SIZE); + if (fmt->flags & V4L2_FMT_FLAG_COMPRESSED) { + fmt->sizeimage[i] = clamp_val(fmt->sizeimage[i], SZ_128K, SZ_8M); + fmt->bytesperline[i] = 0; + } + } + + return 0; +} + +u32 vpu_get_fmt_plane_size(struct vpu_format *fmt, u32 plane_no) +{ + u32 size; + int i; + + if (plane_no >= fmt->mem_planes) + return 0; + + if (fmt->comp_planes == fmt->mem_planes) + return fmt->sizeimage[plane_no]; + if (plane_no < fmt->mem_planes - 1) + return fmt->sizeimage[plane_no]; + + size = fmt->sizeimage[plane_no]; + for (i = fmt->mem_planes; i < fmt->comp_planes; i++) + size += fmt->sizeimage[i]; + + return size; +} + +int vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f, struct vpu_format *fmt) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + int i; + int ret; + + fmt->pixfmt = pixmp->pixelformat; + fmt->type = f->type; + ret = vpu_init_format(inst, fmt); + if (ret < 0) + return ret; + + fmt->width = pixmp->width; + fmt->height = pixmp->height; + if (fmt->width) + fmt->width = vpu_helper_valid_frame_width(inst, fmt->width); + if (fmt->height) + fmt->height = vpu_helper_valid_frame_height(inst, fmt->height); + fmt->field = pixmp->field == V4L2_FIELD_ANY ? V4L2_FIELD_NONE : pixmp->field; + vpu_calc_fmt_bytesperline(f, fmt); + vpu_calc_fmt_sizeimage(inst, fmt); + if ((fmt->flags & V4L2_FMT_FLAG_COMPRESSED) && pixmp->plane_fmt[0].sizeimage) + fmt->sizeimage[0] = clamp_val(pixmp->plane_fmt[0].sizeimage, SZ_128K, SZ_8M); + + pixmp->pixelformat = fmt->pixfmt; + pixmp->width = fmt->width; + pixmp->height = fmt->height; pixmp->flags = fmt->flags; - pixmp->num_planes = fmt->num_planes; - if (pixmp->field == V4L2_FIELD_ANY) - pixmp->field = V4L2_FIELD_NONE; + pixmp->num_planes = fmt->mem_planes; + pixmp->field = fmt->field; + memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); for (i = 0; i < pixmp->num_planes; i++) { - bytesperline = max_t(s32, pixmp->plane_fmt[i].bytesperline, 0); - sizeimage = vpu_helper_get_plane_size(pixmp->pixelformat, - pixmp->width, - pixmp->height, - i, - stride, - pixmp->field > V4L2_FIELD_NONE ? 1 : 0, - &bytesperline); - sizeimage = max_t(s32, pixmp->plane_fmt[i].sizeimage, sizeimage); - pixmp->plane_fmt[i].bytesperline = bytesperline; - pixmp->plane_fmt[i].sizeimage = sizeimage; + pixmp->plane_fmt[i].bytesperline = fmt->bytesperline[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(fmt, i); + memset(pixmp->plane_fmt[i].reserved, 0, sizeof(pixmp->plane_fmt[i].reserved)); } - return fmt; + return 0; } static bool vpu_check_ready(struct vpu_inst *inst, u32 type) @@ -249,8 +327,12 @@ int vpu_process_capture_buffer(struct vpu_inst *inst) struct vb2_v4l2_buffer *vpu_next_src_buf(struct vpu_inst *inst) { - struct vb2_v4l2_buffer *src_buf = v4l2_m2m_next_src_buf(inst->fh.m2m_ctx); + struct vb2_v4l2_buffer *src_buf = NULL; + + if (!inst->fh.m2m_ctx) + return NULL; + src_buf = v4l2_m2m_next_src_buf(inst->fh.m2m_ctx); if (!src_buf || vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_IDLE) return NULL; @@ -273,7 +355,7 @@ void vpu_skip_frame(struct vpu_inst *inst, int count) enum vb2_buffer_state state; int i = 0; - if (count <= 0) + if (count <= 0 || !inst->fh.m2m_ctx) return; while (i < count) { @@ -389,10 +471,10 @@ static int vpu_vb2_queue_setup(struct vb2_queue *vq, cur_fmt = vpu_get_format(inst, vq->type); if (*plane_count) { - if (*plane_count != cur_fmt->num_planes) + if (*plane_count != cur_fmt->mem_planes) return -EINVAL; - for (i = 0; i < cur_fmt->num_planes; i++) { - if (psize[i] < cur_fmt->sizeimage[i]) + for (i = 0; i < cur_fmt->mem_planes; i++) { + if (psize[i] < vpu_get_fmt_plane_size(cur_fmt, i)) return -EINVAL; } return 0; @@ -402,9 +484,9 @@ static int vpu_vb2_queue_setup(struct vb2_queue *vq, *buf_count = max_t(unsigned int, *buf_count, inst->min_buffer_out); else *buf_count = max_t(unsigned int, *buf_count, inst->min_buffer_cap); - *plane_count = cur_fmt->num_planes; - for (i = 0; i < cur_fmt->num_planes; i++) - psize[i] = cur_fmt->sizeimage[i]; + *plane_count = cur_fmt->mem_planes; + for (i = 0; i < cur_fmt->mem_planes; i++) + psize[i] = vpu_get_fmt_plane_size(cur_fmt, i); return 0; } @@ -434,8 +516,8 @@ static int vpu_vb2_buf_prepare(struct vb2_buffer *vb) u32 i; cur_fmt = vpu_get_format(inst, vb->type); - for (i = 0; i < cur_fmt->num_planes; i++) { - if (vpu_get_vb_length(vb, i) < cur_fmt->sizeimage[i]) { + for (i = 0; i < cur_fmt->mem_planes; i++) { + if (vpu_get_vb_length(vb, i) < vpu_get_fmt_plane_size(cur_fmt, i)) { dev_dbg(inst->dev, "[%d] %s buf[%d] is invalid\n", inst->id, vpu_type_name(vb->type), vb->index); vpu_set_buffer_state(vbuf, VPU_BUF_STATE_ERROR); @@ -603,10 +685,6 @@ static int vpu_v4l2_release(struct vpu_inst *inst) inst->workqueue = NULL; } - if (inst->fh.m2m_ctx) { - v4l2_m2m_ctx_release(inst->fh.m2m_ctx); - inst->fh.m2m_ctx = NULL; - } v4l2_ctrl_handler_free(&inst->ctrl_handler); mutex_destroy(&inst->lock); v4l2_fh_del(&inst->fh); @@ -689,6 +767,13 @@ int vpu_v4l2_close(struct file *file) vpu_trace(vpu->dev, "tgid = %d, pid = %d, inst = %p\n", inst->tgid, inst->pid, inst); + vpu_inst_lock(inst); + if (inst->fh.m2m_ctx) { + v4l2_m2m_ctx_release(inst->fh.m2m_ctx); + inst->fh.m2m_ctx = NULL; + } + vpu_inst_unlock(inst); + call_void_vop(inst, release); vpu_inst_unregister(inst); vpu_inst_put(inst); diff --git a/drivers/media/platform/amphion/vpu_v4l2.h b/drivers/media/platform/amphion/vpu_v4l2.h index 795ca33a6a50..ef5de6b66e47 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.h +++ b/drivers/media/platform/amphion/vpu_v4l2.h @@ -16,7 +16,8 @@ unsigned int vpu_get_buffer_state(struct vb2_v4l2_buffer *vbuf); int vpu_v4l2_open(struct file *file, struct vpu_inst *inst); int vpu_v4l2_close(struct file *file); -const struct vpu_format *vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f); +u32 vpu_get_fmt_plane_size(struct vpu_format *fmt, u32 plane_no); +int vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f, struct vpu_format *fmt); int vpu_process_output_buffer(struct vpu_inst *inst); int vpu_process_capture_buffer(struct vpu_inst *inst); struct vb2_v4l2_buffer *vpu_next_src_buf(struct vpu_inst *inst); diff --git a/drivers/media/platform/amphion/vpu_windsor.c b/drivers/media/platform/amphion/vpu_windsor.c index 1526af2ef9da..b245ff6a1102 100644 --- a/drivers/media/platform/amphion/vpu_windsor.c +++ b/drivers/media/platform/amphion/vpu_windsor.c @@ -658,6 +658,7 @@ int vpu_windsor_get_stream_buffer_size(struct vpu_shared_addr *shared) } static struct vpu_pair windsor_cmds[] = { + {VPU_CMD_ID_NOOP, GTB_ENC_CMD_NOOP}, {VPU_CMD_ID_CONFIGURE_CODEC, GTB_ENC_CMD_CONFIGURE_CODEC}, {VPU_CMD_ID_START, GTB_ENC_CMD_STREAM_START}, {VPU_CMD_ID_STOP, GTB_ENC_CMD_STREAM_STOP}, @@ -775,6 +776,8 @@ static int vpu_windsor_fill_yuv_frame(struct vpu_shared_addr *shared, u32 instance, struct vb2_buffer *vb) { + struct vpu_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vpu_format *out_fmt; struct vpu_enc_yuv_desc *desc; struct vb2_v4l2_buffer *vbuf; @@ -782,6 +785,7 @@ static int vpu_windsor_fill_yuv_frame(struct vpu_shared_addr *shared, return -EINVAL; desc = get_yuv_desc(shared, instance); + out_fmt = vpu_get_format(inst, vb->type); vbuf = to_vb2_v4l2_buffer(vb); desc->frame_id = vbuf->sequence; @@ -790,7 +794,10 @@ static int vpu_windsor_fill_yuv_frame(struct vpu_shared_addr *shared, else desc->key_frame = 0; desc->luma_base = vpu_get_vb_phy_addr(vb, 0); - desc->chroma_base = vpu_get_vb_phy_addr(vb, 1); + if (vb->num_planes > 1) + desc->chroma_base = vpu_get_vb_phy_addr(vb, 1); + else + desc->chroma_base = desc->luma_base + out_fmt->sizeimage[0]; return 0; } diff --git a/drivers/media/platform/aspeed/Kconfig b/drivers/media/platform/aspeed/Kconfig index c871eda33570..16c5d8913488 100644 --- a/drivers/media/platform/aspeed/Kconfig +++ b/drivers/media/platform/aspeed/Kconfig @@ -4,6 +4,7 @@ comment "Aspeed media platform drivers" config VIDEO_ASPEED tristate "Aspeed AST2400 and AST2500 Video Engine driver" + depends on ARCH_ASPEED || COMPILE_TEST depends on V4L_PLATFORM_DRIVERS depends on VIDEO_DEV select VIDEOBUF2_DMA_CONTIG diff --git a/drivers/media/platform/aspeed/aspeed-video.c b/drivers/media/platform/aspeed/aspeed-video.c index 20f795ccc11b..794d4dc3a654 100644 --- a/drivers/media/platform/aspeed/aspeed-video.c +++ b/drivers/media/platform/aspeed/aspeed-video.c @@ -33,6 +33,7 @@ #include <media/v4l2-event.h> #include <media/v4l2-ioctl.h> #include <media/videobuf2-dma-contig.h> +#include <uapi/linux/aspeed-video.h> #define ASPEED_VIDEO_V4L2_MIN_BUF_REQ 3 @@ -59,6 +60,7 @@ #define VE_MAX_SRC_BUFFER_SIZE 0x8ca000 /* 1920 * 1200, 32bpp */ #define VE_JPEG_HEADER_SIZE 0x006000 /* 512 * 12 * 4 */ +#define VE_BCD_BUFF_SIZE 0x9000 /* (1920/8) * (1200/8) */ #define VE_PROTECTION_KEY 0x000 #define VE_PROTECTION_KEY_UNLOCK 0x1a038aa8 @@ -107,6 +109,13 @@ #define VE_SCALING_FILTER2 0x020 #define VE_SCALING_FILTER3 0x024 +#define VE_BCD_CTRL 0x02C +#define VE_BCD_CTRL_EN_BCD BIT(0) +#define VE_BCD_CTRL_EN_ABCD BIT(1) +#define VE_BCD_CTRL_EN_CB BIT(2) +#define VE_BCD_CTRL_THR GENMASK(23, 16) +#define VE_BCD_CTRL_ABCD_THR GENMASK(31, 24) + #define VE_CAP_WINDOW 0x030 #define VE_COMP_WINDOW 0x034 #define VE_COMP_PROC_OFFSET 0x038 @@ -115,6 +124,7 @@ #define VE_SRC0_ADDR 0x044 #define VE_SRC_SCANLINE_OFFSET 0x048 #define VE_SRC1_ADDR 0x04c +#define VE_BCD_ADDR 0x050 #define VE_COMP_ADDR 0x054 #define VE_STREAM_BUF_SIZE 0x058 @@ -135,6 +145,8 @@ #define VE_COMP_CTRL_HQ_DCT_CHR GENMASK(26, 22) #define VE_COMP_CTRL_HQ_DCT_LUM GENMASK(31, 27) +#define VE_CB_ADDR 0x06C + #define AST2400_VE_COMP_SIZE_READ_BACK 0x078 #define AST2600_VE_COMP_SIZE_READ_BACK 0x084 @@ -211,6 +223,12 @@ enum { VIDEO_CLOCKS_ON, }; +enum aspeed_video_format { + VIDEO_FMT_STANDARD = 0, + VIDEO_FMT_ASPEED, + VIDEO_FMT_MAX = VIDEO_FMT_ASPEED +}; + // for VE_CTRL_CAPTURE_FMT enum aspeed_video_capture_format { VIDEO_CAP_FMT_YUV_STUDIO_SWING = 0, @@ -245,16 +263,20 @@ struct aspeed_video_perf { /* * struct aspeed_video - driver data * - * res_work: holds the delayed_work for res-detection if unlock - * buffers: holds the list of buffer queued from user + * res_work: holds the delayed_work for res-detection if unlock + * buffers: holds the list of buffer queued from user * flags: holds the state of video * sequence: holds the last number of frame completed * max_compressed_size: holds max compressed stream's size * srcs: holds the buffer information for srcs * jpeg: holds the buffer information for jpeg header + * bcd: holds the buffer information for bcd work * yuv420: a flag raised if JPEG subsampling is 420 + * format: holds the video format + * hq_mode: a flag raised if HQ is enabled. Only for VIDEO_FMT_ASPEED * frame_rate: holds the frame_rate * jpeg_quality: holds jpeq's quality (0~11) + * jpeg_hq_quality: holds hq's quality (1~12) only if hq_mode enabled * frame_bottom: end position of video data in vertical direction * frame_left: start position of video data in horizontal direction * frame_right: end position of video data in horizontal direction @@ -290,10 +312,14 @@ struct aspeed_video { unsigned int max_compressed_size; struct aspeed_video_addr srcs[2]; struct aspeed_video_addr jpeg; + struct aspeed_video_addr bcd; bool yuv420; + enum aspeed_video_format format; + bool hq_mode; unsigned int frame_rate; unsigned int jpeg_quality; + unsigned int jpeg_hq_quality; unsigned int frame_bottom; unsigned int frame_left; @@ -458,8 +484,18 @@ static const struct v4l2_dv_timings_cap aspeed_video_timings_cap = { }, }; +static const char * const format_str[] = {"Standard JPEG", + "Aspeed JPEG"}; + static unsigned int debug; +static bool aspeed_video_alloc_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr, + unsigned int size); + +static void aspeed_video_free_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr); + static void aspeed_video_init_jpeg_table(u32 *table, bool yuv420) { int i; @@ -547,24 +583,39 @@ static int aspeed_video_start_frame(struct aspeed_video *video) unsigned long flags; struct aspeed_video_buffer *buf; u32 seq_ctrl = aspeed_video_read(video, VE_SEQ_CTRL); + bool bcd_buf_need = (video->format != VIDEO_FMT_STANDARD); if (video->v4l2_input_status) { - v4l2_warn(&video->v4l2_dev, "No signal; don't start frame\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "No signal; don't start frame\n"); return 0; } if (!(seq_ctrl & VE_SEQ_CTRL_COMP_BUSY) || !(seq_ctrl & VE_SEQ_CTRL_CAP_BUSY)) { - v4l2_warn(&video->v4l2_dev, "Engine busy; don't start frame\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Engine busy; don't start frame\n"); return -EBUSY; } + if (bcd_buf_need && !video->bcd.size) { + if (!aspeed_video_alloc_buf(video, &video->bcd, + VE_BCD_BUFF_SIZE)) { + dev_err(video->dev, "Failed to allocate BCD buffer\n"); + dev_err(video->dev, "don't start frame\n"); + return -ENOMEM; + } + aspeed_video_write(video, VE_BCD_ADDR, video->bcd.dma); + v4l2_dbg(1, debug, &video->v4l2_dev, "bcd addr(%pad) size(%d)\n", + &video->bcd.dma, video->bcd.size); + } else if (!bcd_buf_need && video->bcd.size) { + aspeed_video_free_buf(video, &video->bcd); + } + spin_lock_irqsave(&video->lock, flags); buf = list_first_entry_or_null(&video->buffers, struct aspeed_video_buffer, link); if (!buf) { spin_unlock_irqrestore(&video->lock, flags); - v4l2_warn(&video->v4l2_dev, "No buffers; don't start frame\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "No buffers; don't start frame\n"); return -EPROTO; } @@ -657,6 +708,24 @@ static void aspeed_video_irq_res_change(struct aspeed_video *video, ulong delay) schedule_delayed_work(&video->res_work, delay); } +static void aspeed_video_swap_src_buf(struct aspeed_video *v) +{ + if (v->format == VIDEO_FMT_STANDARD) + return; + + /* Reset bcd buffer to have a full frame update every 8 frames. */ + if (IS_ALIGNED(v->sequence, 8)) + memset((u8 *)v->bcd.virt, 0x00, VE_BCD_BUFF_SIZE); + + if (v->sequence & 0x01) { + aspeed_video_write(v, VE_SRC0_ADDR, v->srcs[1].dma); + aspeed_video_write(v, VE_SRC1_ADDR, v->srcs[0].dma); + } else { + aspeed_video_write(v, VE_SRC0_ADDR, v->srcs[0].dma); + aspeed_video_write(v, VE_SRC1_ADDR, v->srcs[1].dma); + } +} + static irqreturn_t aspeed_video_irq(int irq, void *arg) { struct aspeed_video *video = arg; @@ -705,6 +774,7 @@ static irqreturn_t aspeed_video_irq(int irq, void *arg) if (sts & VE_INTERRUPT_COMP_COMPLETE) { struct aspeed_video_buffer *buf; + bool empty = true; u32 frame_size = aspeed_video_read(video, video->comp_size_read); @@ -718,13 +788,23 @@ static irqreturn_t aspeed_video_irq(int irq, void *arg) if (buf) { vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size); - if (!list_is_last(&buf->link, &video->buffers)) { + /* + * aspeed_jpeg requires continuous update. + * On the contrary, standard jpeg can keep last buffer + * to always have the latest result. + */ + if (video->format == VIDEO_FMT_STANDARD && + list_is_last(&buf->link, &video->buffers)) { + empty = false; + v4l2_dbg(1, debug, &video->v4l2_dev, "skip to keep last frame updated\n"); + } else { buf->vb.vb2_buf.timestamp = ktime_get_ns(); buf->vb.sequence = video->sequence++; buf->vb.field = V4L2_FIELD_NONE; vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); list_del(&buf->link); + empty = list_empty(&video->buffers); } } spin_unlock(&video->lock); @@ -738,7 +818,10 @@ static irqreturn_t aspeed_video_irq(int irq, void *arg) aspeed_video_write(video, VE_INTERRUPT_STATUS, VE_INTERRUPT_COMP_COMPLETE); sts &= ~VE_INTERRUPT_COMP_COMPLETE; - if (test_bit(VIDEO_STREAMING, &video->flags) && buf) + + aspeed_video_swap_src_buf(video); + + if (test_bit(VIDEO_STREAMING, &video->flags) && !empty) aspeed_video_start_frame(video); } @@ -977,7 +1060,7 @@ static void aspeed_video_get_resolution(struct aspeed_video *video) res_check(video), MODE_DETECT_TIMEOUT); if (!rc) { - v4l2_warn(&video->v4l2_dev, "Timed out; first mode detect\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Timed out; first mode detect\n"); clear_bit(VIDEO_RES_DETECT, &video->flags); return; } @@ -998,7 +1081,7 @@ static void aspeed_video_get_resolution(struct aspeed_video *video) MODE_DETECT_TIMEOUT); clear_bit(VIDEO_RES_DETECT, &video->flags); if (!rc) { - v4l2_warn(&video->v4l2_dev, "Timed out; second mode detect\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Timed out; second mode detect\n"); return; } @@ -1021,7 +1104,7 @@ static void aspeed_video_get_resolution(struct aspeed_video *video) } while (invalid_resolution && (tries++ < INVALID_RESOLUTION_RETRIES)); if (invalid_resolution) { - v4l2_warn(&video->v4l2_dev, "Invalid resolution detected\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Invalid resolution detected\n"); return; } @@ -1085,10 +1168,14 @@ static void aspeed_video_set_resolution(struct aspeed_video *video) FIELD_PREP(VE_TGS_FIRST, video->frame_top) | FIELD_PREP(VE_TGS_LAST, video->frame_bottom + 1)); - aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_INT_DE); + aspeed_video_update(video, VE_CTRL, + VE_CTRL_INT_DE | VE_CTRL_DIRECT_FETCH, + VE_CTRL_INT_DE); } else { v4l2_dbg(1, debug, &video->v4l2_dev, "Capture: Direct Mode\n"); - aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_DIRECT_FETCH); + aspeed_video_update(video, VE_CTRL, + VE_CTRL_INT_DE | VE_CTRL_DIRECT_FETCH, + VE_CTRL_DIRECT_FETCH); } size *= 4; @@ -1121,21 +1208,65 @@ err_mem: aspeed_video_free_buf(video, &video->srcs[0]); } -static void aspeed_video_init_regs(struct aspeed_video *video) +static void aspeed_video_update_regs(struct aspeed_video *video) { - u32 comp_ctrl = VE_COMP_CTRL_RSVD | - FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | - FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); - u32 ctrl = VE_CTRL_AUTO_OR_CURSOR | - FIELD_PREP(VE_CTRL_CAPTURE_FMT, VIDEO_CAP_FMT_YUV_FULL_SWING); - u32 seq_ctrl = video->jpeg_mode; + u8 jpeg_hq_quality = clamp((int)video->jpeg_hq_quality - 1, 0, + ASPEED_VIDEO_JPEG_NUM_QUALITIES - 1); + u32 comp_ctrl = FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | + FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10) | + FIELD_PREP(VE_COMP_CTRL_EN_HQ, video->hq_mode) | + FIELD_PREP(VE_COMP_CTRL_HQ_DCT_LUM, jpeg_hq_quality) | + FIELD_PREP(VE_COMP_CTRL_HQ_DCT_CHR, jpeg_hq_quality | 0x10); + u32 ctrl = 0; + u32 seq_ctrl = 0; + + v4l2_dbg(1, debug, &video->v4l2_dev, "framerate(%d)\n", + video->frame_rate); + v4l2_dbg(1, debug, &video->v4l2_dev, "jpeg format(%s) subsample(%s)\n", + format_str[video->format], + video->yuv420 ? "420" : "444"); + v4l2_dbg(1, debug, &video->v4l2_dev, "compression quality(%d)\n", + video->jpeg_quality); + v4l2_dbg(1, debug, &video->v4l2_dev, "hq_mode(%s) hq_quality(%d)\n", + video->hq_mode ? "on" : "off", video->jpeg_hq_quality); + + if (video->format == VIDEO_FMT_ASPEED) + aspeed_video_update(video, VE_BCD_CTRL, 0, VE_BCD_CTRL_EN_BCD); + else + aspeed_video_update(video, VE_BCD_CTRL, VE_BCD_CTRL_EN_BCD, 0); if (video->frame_rate) ctrl |= FIELD_PREP(VE_CTRL_FRC, video->frame_rate); + if (video->format == VIDEO_FMT_STANDARD) { + comp_ctrl &= ~FIELD_PREP(VE_COMP_CTRL_EN_HQ, video->hq_mode); + seq_ctrl |= video->jpeg_mode; + } + if (video->yuv420) seq_ctrl |= VE_SEQ_CTRL_YUV420; + if (video->jpeg.virt) + aspeed_video_update_jpeg_table(video->jpeg.virt, video->yuv420); + + /* Set control registers */ + aspeed_video_update(video, VE_SEQ_CTRL, + video->jpeg_mode | VE_SEQ_CTRL_YUV420, + seq_ctrl); + aspeed_video_update(video, VE_CTRL, VE_CTRL_FRC, ctrl); + aspeed_video_update(video, VE_COMP_CTRL, + VE_COMP_CTRL_DCT_LUM | VE_COMP_CTRL_DCT_CHR | + VE_COMP_CTRL_EN_HQ | VE_COMP_CTRL_HQ_DCT_LUM | + VE_COMP_CTRL_HQ_DCT_CHR | VE_COMP_CTRL_VQ_4COLOR | + VE_COMP_CTRL_VQ_DCT_ONLY, + comp_ctrl); +} + +static void aspeed_video_init_regs(struct aspeed_video *video) +{ + u32 ctrl = VE_CTRL_AUTO_OR_CURSOR | + FIELD_PREP(VE_CTRL_CAPTURE_FMT, VIDEO_CAP_FMT_YUV_FULL_SWING); + /* Unlock VE registers */ aspeed_video_write(video, VE_PROTECTION_KEY, VE_PROTECTION_KEY_UNLOCK); @@ -1150,9 +1281,8 @@ static void aspeed_video_init_regs(struct aspeed_video *video) aspeed_video_write(video, VE_JPEG_ADDR, video->jpeg.dma); /* Set control registers */ - aspeed_video_write(video, VE_SEQ_CTRL, seq_ctrl); aspeed_video_write(video, VE_CTRL, ctrl); - aspeed_video_write(video, VE_COMP_CTRL, comp_ctrl); + aspeed_video_write(video, VE_COMP_CTRL, VE_COMP_CTRL_RSVD); /* Don't downscale */ aspeed_video_write(video, VE_SCALING_FACTOR, 0x10001000); @@ -1168,6 +1298,8 @@ static void aspeed_video_init_regs(struct aspeed_video *video) FIELD_PREP(VE_MODE_DT_HOR_STABLE, 6) | FIELD_PREP(VE_MODE_DT_VER_STABLE, 6) | FIELD_PREP(VE_MODE_DT_EDG_THROD, 0x65)); + + aspeed_video_write(video, VE_BCD_CTRL, 0); } static void aspeed_video_start(struct aspeed_video *video) @@ -1201,6 +1333,9 @@ static void aspeed_video_stop(struct aspeed_video *video) if (video->srcs[1].size) aspeed_video_free_buf(video, &video->srcs[1]); + if (video->bcd.size) + aspeed_video_free_buf(video, &video->bcd); + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; video->flags = 0; } @@ -1219,10 +1354,12 @@ static int aspeed_video_querycap(struct file *file, void *fh, static int aspeed_video_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) { + struct aspeed_video *video = video_drvdata(file); + if (f->index) return -EINVAL; - f->pixelformat = V4L2_PIX_FMT_JPEG; + f->pixelformat = video->pix_fmt.pixelformat; return 0; } @@ -1237,6 +1374,29 @@ static int aspeed_video_get_format(struct file *file, void *fh, return 0; } +static int aspeed_video_set_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct aspeed_video *video = video_drvdata(file); + + if (vb2_is_busy(&video->queue)) + return -EBUSY; + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_JPEG: + video->format = VIDEO_FMT_STANDARD; + break; + case V4L2_PIX_FMT_AJPG: + video->format = VIDEO_FMT_ASPEED; + break; + default: + return -EINVAL; + } + video->pix_fmt.pixelformat = f->fmt.pix.pixelformat; + + return 0; +} + static int aspeed_video_enum_input(struct file *file, void *fh, struct v4l2_input *inp) { @@ -1454,7 +1614,7 @@ static const struct v4l2_ioctl_ops aspeed_video_ioctl_ops = { .vidioc_enum_fmt_vid_cap = aspeed_video_enum_format, .vidioc_g_fmt_vid_cap = aspeed_video_get_format, - .vidioc_s_fmt_vid_cap = aspeed_video_get_format, + .vidioc_s_fmt_vid_cap = aspeed_video_set_format, .vidioc_try_fmt_vid_cap = aspeed_video_get_format, .vidioc_reqbufs = vb2_ioctl_reqbufs, @@ -1486,27 +1646,6 @@ static const struct v4l2_ioctl_ops aspeed_video_ioctl_ops = { .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; -static void aspeed_video_update_jpeg_quality(struct aspeed_video *video) -{ - u32 comp_ctrl = FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | - FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); - - aspeed_video_update(video, VE_COMP_CTRL, - VE_COMP_CTRL_DCT_LUM | VE_COMP_CTRL_DCT_CHR, - comp_ctrl); -} - -static void aspeed_video_update_subsampling(struct aspeed_video *video) -{ - if (video->jpeg.virt) - aspeed_video_update_jpeg_table(video->jpeg.virt, video->yuv420); - - if (video->yuv420) - aspeed_video_update(video, VE_SEQ_CTRL, 0, VE_SEQ_CTRL_YUV420); - else - aspeed_video_update(video, VE_SEQ_CTRL, VE_SEQ_CTRL_YUV420, 0); -} - static int aspeed_video_set_ctrl(struct v4l2_ctrl *ctrl) { struct aspeed_video *video = container_of(ctrl->handler, @@ -1516,16 +1655,23 @@ static int aspeed_video_set_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_JPEG_COMPRESSION_QUALITY: video->jpeg_quality = ctrl->val; - aspeed_video_update_jpeg_quality(video); + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); break; case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: - if (ctrl->val == V4L2_JPEG_CHROMA_SUBSAMPLING_420) { - video->yuv420 = true; - aspeed_video_update_subsampling(video); - } else { - video->yuv420 = false; - aspeed_video_update_subsampling(video); - } + video->yuv420 = (ctrl->val == V4L2_JPEG_CHROMA_SUBSAMPLING_420); + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); + break; + case V4L2_CID_ASPEED_HQ_MODE: + video->hq_mode = ctrl->val; + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); + break; + case V4L2_CID_ASPEED_HQ_JPEG_QUALITY: + video->jpeg_hq_quality = ctrl->val; + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); break; default: return -EINVAL; @@ -1538,6 +1684,28 @@ static const struct v4l2_ctrl_ops aspeed_video_ctrl_ops = { .s_ctrl = aspeed_video_set_ctrl, }; +static const struct v4l2_ctrl_config aspeed_ctrl_HQ_mode = { + .ops = &aspeed_video_ctrl_ops, + .id = V4L2_CID_ASPEED_HQ_MODE, + .name = "Aspeed HQ Mode", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = false, +}; + +static const struct v4l2_ctrl_config aspeed_ctrl_HQ_jpeg_quality = { + .ops = &aspeed_video_ctrl_ops, + .id = V4L2_CID_ASPEED_HQ_JPEG_QUALITY, + .name = "Aspeed HQ Quality", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 1, + .max = ASPEED_VIDEO_JPEG_NUM_QUALITIES, + .step = 1, + .def = 1, +}; + static void aspeed_video_resolution_work(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); @@ -1552,6 +1720,8 @@ static void aspeed_video_resolution_work(struct work_struct *work) aspeed_video_init_regs(video); + aspeed_video_update_regs(video); + aspeed_video_get_resolution(video); if (video->detected_timings.width != video->active_timings.width || @@ -1662,6 +1832,8 @@ static int aspeed_video_start_streaming(struct vb2_queue *q, video->perf.duration_max = 0; video->perf.duration_min = 0xffffffff; + aspeed_video_update_regs(video); + rc = aspeed_video_start_frame(video); if (rc) { aspeed_video_bufs_done(video, VB2_BUF_STATE_QUEUED); @@ -1683,7 +1855,7 @@ static void aspeed_video_stop_streaming(struct vb2_queue *q) !test_bit(VIDEO_FRAME_INPRG, &video->flags), STOP_TIMEOUT); if (!rc) { - v4l2_warn(&video->v4l2_dev, "Timed out when stopping streaming\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Timed out when stopping streaming\n"); /* * Need to force stop any DMA and try and get HW into a good @@ -1732,9 +1904,29 @@ static const struct vb2_ops aspeed_video_vb2_ops = { static int aspeed_video_debugfs_show(struct seq_file *s, void *data) { struct aspeed_video *v = s->private; + u32 val08; seq_puts(s, "\n"); + seq_puts(s, "Capture:\n"); + val08 = aspeed_video_read(v, VE_CTRL); + if (FIELD_GET(VE_CTRL_DIRECT_FETCH, val08)) { + seq_printf(s, " %-20s:\tDirect fetch\n", "Mode"); + seq_printf(s, " %-20s:\t%s\n", "VGA bpp mode", + FIELD_GET(VE_CTRL_INT_DE, val08) ? "16" : "32"); + } else { + seq_printf(s, " %-20s:\tSync\n", "Mode"); + seq_printf(s, " %-20s:\t%s\n", "Video source", + FIELD_GET(VE_CTRL_SOURCE, val08) ? + "external" : "internal"); + seq_printf(s, " %-20s:\t%s\n", "DE source", + FIELD_GET(VE_CTRL_INT_DE, val08) ? + "internal" : "external"); + seq_printf(s, " %-20s:\t%s\n", "Cursor overlay", + FIELD_GET(VE_CTRL_AUTO_OR_CURSOR, val08) ? + "Without" : "With"); + } + seq_printf(s, " %-20s:\t%s\n", "Signal", v->v4l2_input_status ? "Unlock" : "Lock"); seq_printf(s, " %-20s:\t%d\n", "Width", v->pix_fmt.width); @@ -1743,29 +1935,33 @@ static int aspeed_video_debugfs_show(struct seq_file *s, void *data) seq_puts(s, "\n"); + seq_puts(s, "Compression:\n"); + seq_printf(s, " %-20s:\t%s\n", "Format", format_str[v->format]); + seq_printf(s, " %-20s:\t%s\n", "Subsampling", + v->yuv420 ? "420" : "444"); + seq_printf(s, " %-20s:\t%d\n", "Quality", v->jpeg_quality); + if (v->format == VIDEO_FMT_ASPEED) { + seq_printf(s, " %-20s:\t%s\n", "HQ Mode", + v->hq_mode ? "on" : "off"); + seq_printf(s, " %-20s:\t%d\n", "HQ Quality", + v->hq_mode ? v->jpeg_hq_quality : 0); + } + + seq_puts(s, "\n"); + seq_puts(s, "Performance:\n"); seq_printf(s, " %-20s:\t%d\n", "Frame#", v->sequence); seq_printf(s, " %-20s:\n", "Frame Duration(ms)"); seq_printf(s, " %-18s:\t%d\n", "Now", v->perf.duration); seq_printf(s, " %-18s:\t%d\n", "Min", v->perf.duration_min); seq_printf(s, " %-18s:\t%d\n", "Max", v->perf.duration_max); - seq_printf(s, " %-20s:\t%d\n", "FPS", 1000 / (v->perf.totaltime / v->sequence)); + seq_printf(s, " %-20s:\t%d\n", "FPS", + (v->perf.totaltime && v->sequence) ? + 1000 / (v->perf.totaltime / v->sequence) : 0); return 0; } - -static int aspeed_video_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, aspeed_video_debugfs_show, inode->i_private); -} - -static const struct file_operations aspeed_video_debugfs_ops = { - .owner = THIS_MODULE, - .open = aspeed_video_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(aspeed_video_debugfs); static struct dentry *debugfs_entry; @@ -1779,7 +1975,7 @@ static int aspeed_video_debugfs_create(struct aspeed_video *video) { debugfs_entry = debugfs_create_file(DEVICE_NAME, 0444, NULL, video, - &aspeed_video_debugfs_ops); + &aspeed_video_debugfs_fops); if (!debugfs_entry) aspeed_video_debugfs_remove(video); @@ -1800,6 +1996,7 @@ static int aspeed_video_setup_video(struct aspeed_video *video) struct v4l2_device *v4l2_dev = &video->v4l2_dev; struct vb2_queue *vbq = &video->queue; struct video_device *vdev = &video->vdev; + struct v4l2_ctrl_handler *hdl = &video->ctrl_handler; int rc; video->pix_fmt.pixelformat = V4L2_PIX_FMT_JPEG; @@ -1814,16 +2011,18 @@ static int aspeed_video_setup_video(struct aspeed_video *video) return rc; } - v4l2_ctrl_handler_init(&video->ctrl_handler, 2); - v4l2_ctrl_new_std(&video->ctrl_handler, &aspeed_video_ctrl_ops, + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &aspeed_video_ctrl_ops, V4L2_CID_JPEG_COMPRESSION_QUALITY, 0, ASPEED_VIDEO_JPEG_NUM_QUALITIES - 1, 1, 0); - v4l2_ctrl_new_std_menu(&video->ctrl_handler, &aspeed_video_ctrl_ops, + v4l2_ctrl_new_std_menu(hdl, &aspeed_video_ctrl_ops, V4L2_CID_JPEG_CHROMA_SUBSAMPLING, V4L2_JPEG_CHROMA_SUBSAMPLING_420, mask, V4L2_JPEG_CHROMA_SUBSAMPLING_444); + v4l2_ctrl_new_custom(hdl, &aspeed_ctrl_HQ_mode, NULL); + v4l2_ctrl_new_custom(hdl, &aspeed_ctrl_HQ_jpeg_quality, NULL); - rc = video->ctrl_handler.error; + rc = hdl->error; if (rc) { v4l2_ctrl_handler_free(&video->ctrl_handler); v4l2_device_unregister(v4l2_dev); @@ -1832,7 +2031,7 @@ static int aspeed_video_setup_video(struct aspeed_video *video) return rc; } - v4l2_dev->ctrl_handler = &video->ctrl_handler; + v4l2_dev->ctrl_handler = hdl; vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vbq->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; @@ -1980,6 +2179,7 @@ static int aspeed_video_probe(struct platform_device *pdev) video->comp_size_read = config->comp_size_read; video->frame_rate = 30; + video->jpeg_hq_quality = 1; video->dev = &pdev->dev; spin_lock_init(&video->lock); mutex_init(&video->video_lock); diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig index f399dba62e17..3866ccae07df 100644 --- a/drivers/media/platform/atmel/Kconfig +++ b/drivers/media/platform/atmel/Kconfig @@ -2,42 +2,6 @@ comment "Atmel media platform drivers" -config VIDEO_ATMEL_ISC - tristate "ATMEL Image Sensor Controller (ISC) support" - depends on V4L_PLATFORM_DRIVERS - depends on VIDEO_DEV && COMMON_CLK - depends on ARCH_AT91 || COMPILE_TEST - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select VIDEOBUF2_DMA_CONTIG - select REGMAP_MMIO - select V4L2_FWNODE - select VIDEO_ATMEL_ISC_BASE - help - This module makes the ATMEL Image Sensor Controller available - as a v4l2 device. - -config VIDEO_ATMEL_XISC - tristate "ATMEL eXtended Image Sensor Controller (XISC) support" - depends on V4L_PLATFORM_DRIVERS - depends on VIDEO_DEV && COMMON_CLK - depends on ARCH_AT91 || COMPILE_TEST - select VIDEOBUF2_DMA_CONTIG - select REGMAP_MMIO - select V4L2_FWNODE - select VIDEO_ATMEL_ISC_BASE - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - help - This module makes the ATMEL eXtended Image Sensor Controller - available as a v4l2 device. - -config VIDEO_ATMEL_ISC_BASE - tristate - default n - help - ATMEL ISC and XISC common code base. - config VIDEO_ATMEL_ISI tristate "ATMEL Image Sensor Interface (ISI) support" depends on V4L_PLATFORM_DRIVERS @@ -49,18 +13,3 @@ config VIDEO_ATMEL_ISI This module makes the ATMEL Image Sensor Interface available as a v4l2 device. -config VIDEO_MICROCHIP_CSI2DC - tristate "Microchip CSI2 Demux Controller" - depends on V4L_PLATFORM_DRIVERS - depends on VIDEO_DEV && COMMON_CLK && OF - depends on ARCH_AT91 || COMPILE_TEST - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_FWNODE - help - CSI2 Demux Controller driver. CSI2DC is a helper chip - that converts IDI interface byte stream to a parallel pixel stream. - It supports various RAW formats as input. - - To compile this driver as a module, choose M here: the - module will be called microchip-csi2dc. diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile index 794e8f739287..a14ac6b5211d 100644 --- a/drivers/media/platform/atmel/Makefile +++ b/drivers/media/platform/atmel/Makefile @@ -1,10 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -atmel-isc-objs = atmel-sama5d2-isc.o -atmel-xisc-objs = atmel-sama7g5-isc.o -atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o -obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o -obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o -obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o -obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o diff --git a/drivers/media/platform/chips-media/coda-bit.c b/drivers/media/platform/chips-media/coda-bit.c index 2736a902e3df..ed47d5bd8d61 100644 --- a/drivers/media/platform/chips-media/coda-bit.c +++ b/drivers/media/platform/chips-media/coda-bit.c @@ -854,7 +854,7 @@ static void coda_setup_iram(struct coda_ctx *ctx) /* Only H.264BP and H.263P3 are considered */ iram_info->buf_dbk_y_use = coda_iram_alloc(iram_info, w64); iram_info->buf_dbk_c_use = coda_iram_alloc(iram_info, w64); - if (!iram_info->buf_dbk_c_use) + if (!iram_info->buf_dbk_y_use || !iram_info->buf_dbk_c_use) goto out; iram_info->axi_sram_use |= dbk_bits; @@ -878,7 +878,7 @@ static void coda_setup_iram(struct coda_ctx *ctx) iram_info->buf_dbk_y_use = coda_iram_alloc(iram_info, w128); iram_info->buf_dbk_c_use = coda_iram_alloc(iram_info, w128); - if (!iram_info->buf_dbk_c_use) + if (!iram_info->buf_dbk_y_use || !iram_info->buf_dbk_c_use) goto out; iram_info->axi_sram_use |= dbk_bits; @@ -1084,10 +1084,16 @@ static int coda_start_encoding(struct coda_ctx *ctx) } if (dst_fourcc == V4L2_PIX_FMT_JPEG) { - if (!ctx->params.jpeg_qmat_tab[0]) + if (!ctx->params.jpeg_qmat_tab[0]) { ctx->params.jpeg_qmat_tab[0] = kmalloc(64, GFP_KERNEL); - if (!ctx->params.jpeg_qmat_tab[1]) + if (!ctx->params.jpeg_qmat_tab[0]) + return -ENOMEM; + } + if (!ctx->params.jpeg_qmat_tab[1]) { ctx->params.jpeg_qmat_tab[1] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[1]) + return -ENOMEM; + } coda_set_jpeg_compression_quality(ctx, ctx->params.jpeg_quality); } diff --git a/drivers/media/platform/chips-media/coda-jpeg.c b/drivers/media/platform/chips-media/coda-jpeg.c index 435e7030fc2a..ba8f41002917 100644 --- a/drivers/media/platform/chips-media/coda-jpeg.c +++ b/drivers/media/platform/chips-media/coda-jpeg.c @@ -1052,10 +1052,16 @@ static int coda9_jpeg_start_encoding(struct coda_ctx *ctx) v4l2_err(&dev->v4l2_dev, "error loading Huffman tables\n"); return ret; } - if (!ctx->params.jpeg_qmat_tab[0]) + if (!ctx->params.jpeg_qmat_tab[0]) { ctx->params.jpeg_qmat_tab[0] = kmalloc(64, GFP_KERNEL); - if (!ctx->params.jpeg_qmat_tab[1]) + if (!ctx->params.jpeg_qmat_tab[0]) + return -ENOMEM; + } + if (!ctx->params.jpeg_qmat_tab[1]) { ctx->params.jpeg_qmat_tab[1] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[1]) + return -ENOMEM; + } coda_set_jpeg_compression_quality(ctx, ctx->params.jpeg_quality); return 0; diff --git a/drivers/media/platform/mediatek/jpeg/Makefile b/drivers/media/platform/mediatek/jpeg/Makefile index 76c33aad0f3f..26e84852523e 100644 --- a/drivers/media/platform/mediatek/jpeg/Makefile +++ b/drivers/media/platform/mediatek/jpeg/Makefile @@ -1,6 +1,10 @@ # SPDX-License-Identifier: GPL-2.0-only -mtk_jpeg-objs := mtk_jpeg_core.o \ - mtk_jpeg_dec_hw.o \ - mtk_jpeg_dec_parse.o \ - mtk_jpeg_enc_hw.o -obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk_jpeg.o +obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk_jpeg.o \ + mtk-jpeg-enc-hw.o \ + mtk-jpeg-dec-hw.o + +mtk_jpeg-y := mtk_jpeg_core.o \ + mtk_jpeg_dec_parse.o + +mtk-jpeg-enc-hw-y := mtk_jpeg_enc_hw.o +mtk-jpeg-dec-hw-y := mtk_jpeg_dec_hw.o diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c index 3071b61946c3..969516a940ba 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c @@ -104,11 +104,11 @@ static struct mtk_jpeg_fmt mtk_jpeg_dec_formats[] = { #define MTK_JPEG_ENC_NUM_FORMATS ARRAY_SIZE(mtk_jpeg_enc_formats) #define MTK_JPEG_DEC_NUM_FORMATS ARRAY_SIZE(mtk_jpeg_dec_formats) +#define MTK_JPEG_MAX_RETRY_TIME 5000 -struct mtk_jpeg_src_buf { - struct vb2_v4l2_buffer b; - struct list_head list; - struct mtk_jpeg_dec_param dec_param; +enum { + MTK_JPEG_BUF_FLAGS_INIT = 0, + MTK_JPEG_BUF_FLAGS_LAST_FRAME = 1, }; static int debug; @@ -586,6 +586,31 @@ static int mtk_jpeg_enc_s_selection(struct file *file, void *priv, return 0; } +static int mtk_jpeg_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct v4l2_fh *fh = file->private_data; + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + struct vb2_queue *vq; + struct vb2_buffer *vb; + struct mtk_jpeg_src_buf *jpeg_src_buf; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + goto end; + + vq = v4l2_m2m_get_vq(fh->m2m_ctx, buf->type); + if (buf->index >= vq->num_buffers) { + dev_err(ctx->jpeg->dev, "buffer index out of range\n"); + return -EINVAL; + } + + vb = vq->bufs[buf->index]; + jpeg_src_buf = mtk_jpeg_vb2_to_srcbuf(vb); + jpeg_src_buf->bs_size = buf->m.planes[0].bytesused; + +end: + return v4l2_m2m_qbuf(file, fh->m2m_ctx, buf); +} + static const struct v4l2_ioctl_ops mtk_jpeg_enc_ioctl_ops = { .vidioc_querycap = mtk_jpeg_querycap, .vidioc_enum_fmt_vid_cap = mtk_jpeg_enum_fmt_vid_cap, @@ -611,6 +636,9 @@ static const struct v4l2_ioctl_ops mtk_jpeg_enc_ioctl_ops = { .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_encoder_cmd = v4l2_m2m_ioctl_encoder_cmd, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, }; static const struct v4l2_ioctl_ops mtk_jpeg_dec_ioctl_ops = { @@ -623,7 +651,7 @@ static const struct v4l2_ioctl_ops mtk_jpeg_dec_ioctl_ops = { .vidioc_g_fmt_vid_out_mplane = mtk_jpeg_g_fmt_vid_mplane, .vidioc_s_fmt_vid_cap_mplane = mtk_jpeg_s_fmt_vid_cap_mplane, .vidioc_s_fmt_vid_out_mplane = mtk_jpeg_s_fmt_vid_out_mplane, - .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_qbuf = mtk_jpeg_qbuf, .vidioc_subscribe_event = mtk_jpeg_subscribe_event, .vidioc_g_selection = mtk_jpeg_dec_g_selection, @@ -637,6 +665,9 @@ static const struct v4l2_ioctl_ops mtk_jpeg_dec_ioctl_ops = { .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_decoder_cmd = v4l2_m2m_ioctl_decoder_cmd, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, }; static int mtk_jpeg_queue_setup(struct vb2_queue *q, @@ -678,7 +709,7 @@ static int mtk_jpeg_buf_prepare(struct vb2_buffer *vb) { struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); struct mtk_jpeg_q_data *q_data = NULL; - struct v4l2_plane_pix_format plane_fmt; + struct v4l2_plane_pix_format plane_fmt = {}; int i; q_data = mtk_jpeg_get_q_data(ctx, vb->vb2_queue->type); @@ -905,6 +936,148 @@ static int mtk_jpeg_set_dec_dst(struct mtk_jpeg_ctx *ctx, return 0; } +static int mtk_jpegenc_get_hw(struct mtk_jpeg_ctx *ctx) +{ + struct mtk_jpegenc_comp_dev *comp_jpeg; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + unsigned long flags; + int hw_id = -1; + int i; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) { + comp_jpeg = jpeg->enc_hw_dev[i]; + if (comp_jpeg->hw_state == MTK_JPEG_HW_IDLE) { + hw_id = i; + comp_jpeg->hw_state = MTK_JPEG_HW_BUSY; + break; + } + } + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return hw_id; +} + +static int mtk_jpegenc_set_hw_param(struct mtk_jpeg_ctx *ctx, + int hw_id, + struct vb2_v4l2_buffer *src_buf, + struct vb2_v4l2_buffer *dst_buf) +{ + struct mtk_jpegenc_comp_dev *jpeg = ctx->jpeg->enc_hw_dev[hw_id]; + + jpeg->hw_param.curr_ctx = ctx; + jpeg->hw_param.src_buffer = src_buf; + jpeg->hw_param.dst_buffer = dst_buf; + + return 0; +} + +static int mtk_jpegenc_put_hw(struct mtk_jpeg_dev *jpeg, int hw_id) +{ + unsigned long flags; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + jpeg->enc_hw_dev[hw_id]->hw_state = MTK_JPEG_HW_IDLE; + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return 0; +} + +static void mtk_jpegenc_worker(struct work_struct *work) +{ + struct mtk_jpegenc_comp_dev *comp_jpeg[MTK_JPEGENC_HW_MAX]; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct mtk_jpeg_src_buf *jpeg_dst_buf; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + int ret, i, hw_id = 0; + unsigned long flags; + + struct mtk_jpeg_ctx *ctx = container_of(work, + struct mtk_jpeg_ctx, + jpeg_work); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) + comp_jpeg[i] = jpeg->enc_hw_dev[i]; + i = 0; + +retry_select: + hw_id = mtk_jpegenc_get_hw(ctx); + if (hw_id < 0) { + ret = wait_event_interruptible(jpeg->enc_hw_wq, + atomic_read(&jpeg->enchw_rdy) > 0); + if (ret != 0 || (i++ > MTK_JPEG_MAX_RETRY_TIME)) { + dev_err(jpeg->dev, "%s : %d, all HW are busy\n", + __func__, __LINE__); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return; + } + + goto retry_select; + } + + atomic_dec(&jpeg->enchw_rdy); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src_buf) + goto getbuf_fail; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!dst_buf) + goto getbuf_fail; + + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + mtk_jpegenc_set_hw_param(ctx, hw_id, src_buf, dst_buf); + ret = pm_runtime_get_sync(comp_jpeg[hw_id]->dev); + if (ret < 0) { + dev_err(jpeg->dev, "%s : %d, pm_runtime_get_sync fail !!!\n", + __func__, __LINE__); + goto enc_end; + } + + ret = clk_prepare_enable(comp_jpeg[hw_id]->venc_clk.clks->clk); + if (ret) { + dev_err(jpeg->dev, "%s : %d, jpegenc clk_prepare_enable fail\n", + __func__, __LINE__); + goto enc_end; + } + + schedule_delayed_work(&comp_jpeg[hw_id]->job_timeout_work, + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + + spin_lock_irqsave(&comp_jpeg[hw_id]->hw_lock, flags); + jpeg_dst_buf = mtk_jpeg_vb2_to_srcbuf(&dst_buf->vb2_buf); + jpeg_dst_buf->curr_ctx = ctx; + jpeg_dst_buf->frame_num = ctx->total_frame_num; + ctx->total_frame_num++; + mtk_jpeg_enc_reset(comp_jpeg[hw_id]->reg_base); + mtk_jpeg_set_enc_dst(ctx, + comp_jpeg[hw_id]->reg_base, + &dst_buf->vb2_buf); + mtk_jpeg_set_enc_src(ctx, + comp_jpeg[hw_id]->reg_base, + &src_buf->vb2_buf); + mtk_jpeg_set_enc_params(ctx, comp_jpeg[hw_id]->reg_base); + mtk_jpeg_enc_start(comp_jpeg[hw_id]->reg_base); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + spin_unlock_irqrestore(&comp_jpeg[hw_id]->hw_lock, flags); + + return; + +enc_end: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); +getbuf_fail: + atomic_inc(&jpeg->enchw_rdy); + mtk_jpegenc_put_hw(jpeg, hw_id); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); +} + static void mtk_jpeg_enc_device_run(void *priv) { struct mtk_jpeg_ctx *ctx = priv; @@ -922,7 +1095,7 @@ static void mtk_jpeg_enc_device_run(void *priv) goto enc_end; schedule_delayed_work(&jpeg->job_timeout_work, - msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); spin_lock_irqsave(&jpeg->hw_lock, flags); @@ -947,6 +1120,189 @@ enc_end: v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); } +static void mtk_jpeg_multicore_enc_device_run(void *priv) +{ + struct mtk_jpeg_ctx *ctx = priv; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + queue_work(jpeg->workqueue, &ctx->jpeg_work); +} + +static int mtk_jpegdec_get_hw(struct mtk_jpeg_ctx *ctx) +{ + struct mtk_jpegdec_comp_dev *comp_jpeg; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + unsigned long flags; + int hw_id = -1; + int i; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) { + comp_jpeg = jpeg->dec_hw_dev[i]; + if (comp_jpeg->hw_state == MTK_JPEG_HW_IDLE) { + hw_id = i; + comp_jpeg->hw_state = MTK_JPEG_HW_BUSY; + break; + } + } + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return hw_id; +} + +static int mtk_jpegdec_put_hw(struct mtk_jpeg_dev *jpeg, int hw_id) +{ + unsigned long flags; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + jpeg->dec_hw_dev[hw_id]->hw_state = + MTK_JPEG_HW_IDLE; + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return 0; +} + +static int mtk_jpegdec_set_hw_param(struct mtk_jpeg_ctx *ctx, + int hw_id, + struct vb2_v4l2_buffer *src_buf, + struct vb2_v4l2_buffer *dst_buf) +{ + struct mtk_jpegdec_comp_dev *jpeg = + ctx->jpeg->dec_hw_dev[hw_id]; + + jpeg->hw_param.curr_ctx = ctx; + jpeg->hw_param.src_buffer = src_buf; + jpeg->hw_param.dst_buffer = dst_buf; + + return 0; +} + +static void mtk_jpegdec_worker(struct work_struct *work) +{ + struct mtk_jpeg_ctx *ctx = container_of(work, struct mtk_jpeg_ctx, + jpeg_work); + struct mtk_jpegdec_comp_dev *comp_jpeg[MTK_JPEGDEC_HW_MAX]; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct mtk_jpeg_src_buf *jpeg_src_buf, *jpeg_dst_buf; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + int ret, i, hw_id = 0; + struct mtk_jpeg_bs bs; + struct mtk_jpeg_fb fb; + unsigned long flags; + + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) + comp_jpeg[i] = jpeg->dec_hw_dev[i]; + i = 0; + +retry_select: + hw_id = mtk_jpegdec_get_hw(ctx); + if (hw_id < 0) { + ret = wait_event_interruptible_timeout(jpeg->dec_hw_wq, + atomic_read(&jpeg->dechw_rdy) > 0, + MTK_JPEG_HW_TIMEOUT_MSEC); + if (ret != 0 || (i++ > MTK_JPEG_MAX_RETRY_TIME)) { + dev_err(jpeg->dev, "%s : %d, all HW are busy\n", + __func__, __LINE__); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return; + } + + goto retry_select; + } + + atomic_dec(&jpeg->dechw_rdy); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src_buf) + goto getbuf_fail; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!dst_buf) + goto getbuf_fail; + + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + jpeg_src_buf = mtk_jpeg_vb2_to_srcbuf(&src_buf->vb2_buf); + jpeg_dst_buf = mtk_jpeg_vb2_to_srcbuf(&dst_buf->vb2_buf); + + if (mtk_jpeg_check_resolution_change(ctx, + &jpeg_src_buf->dec_param)) { + mtk_jpeg_queue_src_chg_event(ctx); + ctx->state = MTK_JPEG_SOURCE_CHANGE; + goto dec_end; + } + + jpeg_src_buf->curr_ctx = ctx; + jpeg_src_buf->frame_num = ctx->total_frame_num; + jpeg_dst_buf->curr_ctx = ctx; + jpeg_dst_buf->frame_num = ctx->total_frame_num; + + mtk_jpegdec_set_hw_param(ctx, hw_id, src_buf, dst_buf); + ret = pm_runtime_get_sync(comp_jpeg[hw_id]->dev); + if (ret < 0) { + dev_err(jpeg->dev, "%s : %d, pm_runtime_get_sync fail !!!\n", + __func__, __LINE__); + goto dec_end; + } + + ret = clk_prepare_enable(comp_jpeg[hw_id]->jdec_clk.clks->clk); + if (ret) { + dev_err(jpeg->dev, "%s : %d, jpegdec clk_prepare_enable fail\n", + __func__, __LINE__); + goto clk_end; + } + + schedule_delayed_work(&comp_jpeg[hw_id]->job_timeout_work, + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + + mtk_jpeg_set_dec_src(ctx, &src_buf->vb2_buf, &bs); + if (mtk_jpeg_set_dec_dst(ctx, + &jpeg_src_buf->dec_param, + &dst_buf->vb2_buf, &fb)) { + dev_err(jpeg->dev, "%s : %d, mtk_jpeg_set_dec_dst fail\n", + __func__, __LINE__); + goto setdst_end; + } + + spin_lock_irqsave(&comp_jpeg[hw_id]->hw_lock, flags); + ctx->total_frame_num++; + mtk_jpeg_dec_reset(comp_jpeg[hw_id]->reg_base); + mtk_jpeg_dec_set_config(comp_jpeg[hw_id]->reg_base, + &jpeg_src_buf->dec_param, + jpeg_src_buf->bs_size, + &bs, + &fb); + mtk_jpeg_dec_start(comp_jpeg[hw_id]->reg_base); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + spin_unlock_irqrestore(&comp_jpeg[hw_id]->hw_lock, flags); + + return; + +setdst_end: + clk_disable_unprepare(comp_jpeg[hw_id]->jdec_clk.clks->clk); +clk_end: + pm_runtime_put(comp_jpeg[hw_id]->dev); +dec_end: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); +getbuf_fail: + atomic_inc(&jpeg->dechw_rdy); + mtk_jpegdec_put_hw(jpeg, hw_id); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); +} + +static void mtk_jpeg_multicore_dec_device_run(void *priv) +{ + struct mtk_jpeg_ctx *ctx = priv; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + queue_work(jpeg->workqueue, &ctx->jpeg_work); +} + static void mtk_jpeg_dec_device_run(void *priv) { struct mtk_jpeg_ctx *ctx = priv; @@ -984,8 +1340,10 @@ static void mtk_jpeg_dec_device_run(void *priv) spin_lock_irqsave(&jpeg->hw_lock, flags); mtk_jpeg_dec_reset(jpeg->reg_base); mtk_jpeg_dec_set_config(jpeg->reg_base, - &jpeg_src_buf->dec_param, &bs, &fb); - + &jpeg_src_buf->dec_param, + jpeg_src_buf->bs_size, + &bs, + &fb); mtk_jpeg_dec_start(jpeg->reg_base); spin_unlock_irqrestore(&jpeg->hw_lock, flags); return; @@ -1009,6 +1367,14 @@ static const struct v4l2_m2m_ops mtk_jpeg_enc_m2m_ops = { .device_run = mtk_jpeg_enc_device_run, }; +static const struct v4l2_m2m_ops mtk_jpeg_multicore_enc_m2m_ops = { + .device_run = mtk_jpeg_multicore_enc_device_run, +}; + +static const struct v4l2_m2m_ops mtk_jpeg_multicore_dec_m2m_ops = { + .device_run = mtk_jpeg_multicore_dec_device_run, +}; + static const struct v4l2_m2m_ops mtk_jpeg_dec_m2m_ops = { .device_run = mtk_jpeg_dec_device_run, .job_ready = mtk_jpeg_dec_job_ready, @@ -1209,6 +1575,14 @@ static int mtk_jpeg_open(struct file *file) goto free; } + if (jpeg->is_jpgenc_multihw) + INIT_WORK(&ctx->jpeg_work, mtk_jpegenc_worker); + + if (jpeg->is_jpgdec_multihw) + INIT_WORK(&ctx->jpeg_work, mtk_jpegdec_worker); + + INIT_LIST_HEAD(&ctx->dst_done_queue); + spin_lock_init(&ctx->done_queue_lock); v4l2_fh_init(&ctx->fh, vfd); file->private_data = &ctx->fh; v4l2_fh_add(&ctx->fh); @@ -1230,6 +1604,7 @@ static int mtk_jpeg_open(struct file *file) } else { v4l2_ctrl_handler_init(&ctx->ctrl_hdl, 0); } + mtk_jpeg_set_default_params(ctx); mutex_unlock(&jpeg->lock); return 0; @@ -1310,38 +1685,51 @@ static int mtk_jpeg_probe(struct platform_device *pdev) spin_lock_init(&jpeg->hw_lock); jpeg->dev = &pdev->dev; jpeg->variant = of_device_get_match_data(jpeg->dev); - INIT_DELAYED_WORK(&jpeg->job_timeout_work, mtk_jpeg_job_timeout_work); - jpeg->reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(jpeg->reg_base)) { - ret = PTR_ERR(jpeg->reg_base); - return ret; + ret = devm_of_platform_populate(&pdev->dev); + if (ret) { + v4l2_err(&jpeg->v4l2_dev, "Master of platform populate failed."); + return -EINVAL; } - jpeg_irq = platform_get_irq(pdev, 0); - if (jpeg_irq < 0) - return jpeg_irq; + if (list_empty(&pdev->dev.devres_head)) { + INIT_DELAYED_WORK(&jpeg->job_timeout_work, + mtk_jpeg_job_timeout_work); - ret = devm_request_irq(&pdev->dev, jpeg_irq, - jpeg->variant->irq_handler, 0, pdev->name, jpeg); - if (ret) { - dev_err(&pdev->dev, "Failed to request jpeg_irq %d (%d)\n", - jpeg_irq, ret); - goto err_req_irq; - } + jpeg->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpeg->reg_base)) { + ret = PTR_ERR(jpeg->reg_base); + return ret; + } - ret = devm_clk_bulk_get(jpeg->dev, jpeg->variant->num_clks, - jpeg->variant->clks); - if (ret) { - dev_err(&pdev->dev, "Failed to init clk, err %d\n", ret); - goto err_clk_init; + jpeg_irq = platform_get_irq(pdev, 0); + if (jpeg_irq < 0) + return jpeg_irq; + + ret = devm_request_irq(&pdev->dev, + jpeg_irq, + jpeg->variant->irq_handler, + 0, + pdev->name, jpeg); + if (ret) { + dev_err(&pdev->dev, "Failed to request jpeg_irq %d (%d)\n", + jpeg_irq, ret); + return ret; + } + + ret = devm_clk_bulk_get(jpeg->dev, + jpeg->variant->num_clks, + jpeg->variant->clks); + if (ret) { + dev_err(&pdev->dev, "Failed to init clk\n"); + return ret; + } } ret = v4l2_device_register(&pdev->dev, &jpeg->v4l2_dev); if (ret) { dev_err(&pdev->dev, "Failed to register v4l2 device\n"); - ret = -EINVAL; - goto err_dev_register; + return -EINVAL; } jpeg->m2m_dev = v4l2_m2m_init(jpeg->variant->m2m_ops); @@ -1399,12 +1787,6 @@ err_vfd_jpeg_alloc: err_m2m_init: v4l2_device_unregister(&jpeg->v4l2_dev); -err_dev_register: - -err_clk_init: - -err_req_irq: - return ret; } @@ -1494,6 +1876,29 @@ static const struct mtk_jpeg_variant mtk_jpeg_drvdata = { .cap_q_default_fourcc = V4L2_PIX_FMT_JPEG, }; +static struct mtk_jpeg_variant mtk8195_jpegenc_drvdata = { + .formats = mtk_jpeg_enc_formats, + .num_formats = MTK_JPEG_ENC_NUM_FORMATS, + .qops = &mtk_jpeg_enc_qops, + .m2m_ops = &mtk_jpeg_multicore_enc_m2m_ops, + .dev_name = "mtk-jpeg-enc", + .ioctl_ops = &mtk_jpeg_enc_ioctl_ops, + .out_q_default_fourcc = V4L2_PIX_FMT_YUYV, + .cap_q_default_fourcc = V4L2_PIX_FMT_JPEG, +}; + +static const struct mtk_jpeg_variant mtk8195_jpegdec_drvdata = { + .formats = mtk_jpeg_dec_formats, + .num_formats = MTK_JPEG_DEC_NUM_FORMATS, + .qops = &mtk_jpeg_dec_qops, + .m2m_ops = &mtk_jpeg_multicore_dec_m2m_ops, + .dev_name = "mtk-jpeg-dec", + .ioctl_ops = &mtk_jpeg_dec_ioctl_ops, + .out_q_default_fourcc = V4L2_PIX_FMT_JPEG, + .cap_q_default_fourcc = V4L2_PIX_FMT_YUV420M, +}; + +#if defined(CONFIG_OF) static const struct of_device_id mtk_jpeg_match[] = { { .compatible = "mediatek,mt8173-jpgdec", @@ -1507,17 +1912,26 @@ static const struct of_device_id mtk_jpeg_match[] = { .compatible = "mediatek,mtk-jpgenc", .data = &mtk_jpeg_drvdata, }, + { + .compatible = "mediatek,mt8195-jpgenc", + .data = &mtk8195_jpegenc_drvdata, + }, + { + .compatible = "mediatek,mt8195-jpgdec", + .data = &mtk8195_jpegdec_drvdata, + }, {}, }; MODULE_DEVICE_TABLE(of, mtk_jpeg_match); +#endif static struct platform_driver mtk_jpeg_driver = { .probe = mtk_jpeg_probe, .remove = mtk_jpeg_remove, .driver = { .name = MTK_JPEG_NAME, - .of_match_table = mtk_jpeg_match, + .of_match_table = of_match_ptr(mtk_jpeg_match), .pm = &mtk_jpeg_pm_ops, }, }; diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h index 3e4811a41ba2..b9126476be8f 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h @@ -9,14 +9,16 @@ #ifndef _MTK_JPEG_CORE_H #define _MTK_JPEG_CORE_H +#include <linux/clk.h> #include <linux/interrupt.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-fh.h> +#include <media/videobuf2-v4l2.h> -#define MTK_JPEG_NAME "mtk-jpeg" +#include "mtk_jpeg_dec_hw.h" -#define MTK_JPEG_COMP_MAX 3 +#define MTK_JPEG_NAME "mtk-jpeg" #define MTK_JPEG_FMT_FLAG_OUTPUT BIT(0) #define MTK_JPEG_FMT_FLAG_CAPTURE BIT(1) @@ -74,6 +76,115 @@ struct mtk_jpeg_variant { u32 cap_q_default_fourcc; }; +struct mtk_jpeg_src_buf { + u32 frame_num; + struct vb2_v4l2_buffer b; + struct list_head list; + u32 bs_size; + struct mtk_jpeg_dec_param dec_param; + + struct mtk_jpeg_ctx *curr_ctx; +}; + +enum mtk_jpeg_hw_state { + MTK_JPEG_HW_IDLE = 0, + MTK_JPEG_HW_BUSY = 1, +}; + +struct mtk_jpeg_hw_param { + struct vb2_v4l2_buffer *src_buffer; + struct vb2_v4l2_buffer *dst_buffer; + struct mtk_jpeg_ctx *curr_ctx; +}; + +enum mtk_jpegenc_hw_id { + MTK_JPEGENC_HW0, + MTK_JPEGENC_HW1, + MTK_JPEGENC_HW_MAX, +}; + +enum mtk_jpegdec_hw_id { + MTK_JPEGDEC_HW0, + MTK_JPEGDEC_HW1, + MTK_JPEGDEC_HW2, + MTK_JPEGDEC_HW_MAX, +}; + +/** + * struct mtk_jpegenc_clk - Structure used to store vcodec clock information + * @clks: JPEG encode clock + * @clk_num: JPEG encode clock numbers + */ +struct mtk_jpegenc_clk { + struct clk_bulk_data *clks; + int clk_num; +}; + +/** + * struct mtk_jpegdec_clk - Structure used to store vcodec clock information + * @clks: JPEG decode clock + * @clk_num: JPEG decode clock numbers + */ +struct mtk_jpegdec_clk { + struct clk_bulk_data *clks; + int clk_num; +}; + +/** + * struct mtk_jpegenc_comp_dev - JPEG COREX abstraction + * @dev: JPEG device + * @plat_dev: platform device data + * @reg_base: JPEG registers mapping + * @master_dev: mtk_jpeg_dev device + * @venc_clk: jpeg encode clock + * @jpegenc_irq: jpeg encode irq num + * @job_timeout_work: encode timeout workqueue + * @hw_param: jpeg encode hw parameters + * @hw_rdy: record hw ready + * @hw_state: record hw state + * @hw_lock: spinlock protecting the hw device resource + */ +struct mtk_jpegenc_comp_dev { + struct device *dev; + struct platform_device *plat_dev; + void __iomem *reg_base; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegenc_clk venc_clk; + int jpegenc_irq; + struct delayed_work job_timeout_work; + struct mtk_jpeg_hw_param hw_param; + enum mtk_jpeg_hw_state hw_state; + /* spinlock protecting the hw device resource */ + spinlock_t hw_lock; +}; + +/** + * struct mtk_jpegdec_comp_dev - JPEG COREX abstraction + * @dev: JPEG device + * @plat_dev: platform device data + * @reg_base: JPEG registers mapping + * @master_dev: mtk_jpeg_dev device + * @jdec_clk: mtk_jpegdec_clk + * @jpegdec_irq: jpeg decode irq num + * @job_timeout_work: decode timeout workqueue + * @hw_param: jpeg decode hw parameters + * @hw_state: record hw state + * @hw_lock: spinlock protecting hw + */ +struct mtk_jpegdec_comp_dev { + struct device *dev; + struct platform_device *plat_dev; + void __iomem *reg_base; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegdec_clk jdec_clk; + int jpegdec_irq; + struct delayed_work job_timeout_work; + struct mtk_jpeg_hw_param hw_param; + enum mtk_jpeg_hw_state hw_state; + /* spinlock protecting the hw device resource */ + spinlock_t hw_lock; +}; + /** * struct mtk_jpeg_dev - JPEG IP abstraction * @lock: the mutex protecting this structure @@ -87,6 +198,17 @@ struct mtk_jpeg_variant { * @reg_base: JPEG registers mapping * @job_timeout_work: IRQ timeout structure * @variant: driver variant to be used + * @reg_encbase: jpg encode register base addr + * @enc_hw_dev: jpg encode hardware device + * @is_jpgenc_multihw: the flag of multi-hw core + * @enc_hw_wq: jpg encode wait queue + * @enchw_rdy: jpg encode hw ready flag + * @reg_decbase: jpg decode register base addr + * @dec_hw_dev: jpg decode hardware device + * @is_jpgdec_multihw: the flag of dec multi-hw core + * @dec_hw_wq: jpg decode wait queue + * @dec_workqueue: jpg decode work queue + * @dechw_rdy: jpg decode hw ready flag */ struct mtk_jpeg_dev { struct mutex lock; @@ -100,6 +222,19 @@ struct mtk_jpeg_dev { void __iomem *reg_base; struct delayed_work job_timeout_work; const struct mtk_jpeg_variant *variant; + + void __iomem *reg_encbase[MTK_JPEGENC_HW_MAX]; + struct mtk_jpegenc_comp_dev *enc_hw_dev[MTK_JPEGENC_HW_MAX]; + bool is_jpgenc_multihw; + wait_queue_head_t enc_hw_wq; + atomic_t enchw_rdy; + + void __iomem *reg_decbase[MTK_JPEGDEC_HW_MAX]; + struct mtk_jpegdec_comp_dev *dec_hw_dev[MTK_JPEGDEC_HW_MAX]; + bool is_jpgdec_multihw; + wait_queue_head_t dec_hw_wq; + struct workqueue_struct *dec_workqueue; + atomic_t dechw_rdy; }; /** @@ -138,15 +273,20 @@ struct mtk_jpeg_q_data { /** * struct mtk_jpeg_ctx - the device context data - * @jpeg: JPEG IP device for this context - * @out_q: source (output) queue information - * @cap_q: destination (capture) queue queue information - * @fh: V4L2 file handle - * @state: state of the context - * @enable_exif: enable exif mode of jpeg encoder - * @enc_quality: jpeg encoder quality - * @restart_interval: jpeg encoder restart interval - * @ctrl_hdl: controls handler + * @jpeg: JPEG IP device for this context + * @out_q: source (output) queue information + * @cap_q: destination queue information + * @fh: V4L2 file handle + * @state: state of the context + * @enable_exif: enable exif mode of jpeg encoder + * @enc_quality: jpeg encoder quality + * @restart_interval: jpeg encoder restart interval + * @ctrl_hdl: controls handler + * @jpeg_work: jpeg encoder workqueue + * @total_frame_num: encoded frame number + * @dst_done_queue: encoded frame buffer queue + * @done_queue_lock: encoded frame operation spinlock + * @last_done_frame_num: the last encoded frame number */ struct mtk_jpeg_ctx { struct mtk_jpeg_dev *jpeg; @@ -158,6 +298,13 @@ struct mtk_jpeg_ctx { u8 enc_quality; u8 restart_interval; struct v4l2_ctrl_handler ctrl_hdl; + + struct work_struct jpeg_work; + u32 total_frame_num; + struct list_head dst_done_queue; + /* spinlock protecting the encode done buffer */ + spinlock_t done_queue_lock; + u32 last_done_frame_num; }; #endif /* _MTK_JPEG_CORE_H */ diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c index afbbfd5d02bc..8c07fa02fd9a 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c @@ -5,10 +5,26 @@ * Rick Chang <rick.chang@mediatek.com> */ +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/io.h> #include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <media/media-device.h> #include <media/videobuf2-core.h> - +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> + +#include "mtk_jpeg_core.h" #include "mtk_jpeg_dec_hw.h" #define MTK_JPEG_DUNUM_MASK(val) (((val) - 1) & 0x3) @@ -23,6 +39,16 @@ enum mtk_jpeg_color { MTK_JPEG_COLOR_400 = 0x00110000 }; +#if defined(CONFIG_OF) +static const struct of_device_id mtk_jpegdec_hw_ids[] = { + { + .compatible = "mediatek,mt8195-jpgdec-hw", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_jpegdec_hw_ids); +#endif + static inline int mtk_jpeg_verify_align(u32 val, int align, u32 reg) { if (val & (align - 1)) { @@ -188,6 +214,7 @@ int mtk_jpeg_dec_fill_param(struct mtk_jpeg_dec_param *param) return 0; } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_fill_param); u32 mtk_jpeg_dec_get_int_status(void __iomem *base) { @@ -199,6 +226,7 @@ u32 mtk_jpeg_dec_get_int_status(void __iomem *base) return ret; } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_get_int_status); u32 mtk_jpeg_dec_enum_result(u32 irq_result) { @@ -215,11 +243,13 @@ u32 mtk_jpeg_dec_enum_result(u32 irq_result) return MTK_JPEG_DEC_RESULT_ERROR_UNKNOWN; } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_enum_result); void mtk_jpeg_dec_start(void __iomem *base) { writel(0, base + JPGDEC_REG_TRIG); } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_start); static void mtk_jpeg_dec_soft_reset(void __iomem *base) { @@ -239,6 +269,7 @@ void mtk_jpeg_dec_reset(void __iomem *base) mtk_jpeg_dec_soft_reset(base); mtk_jpeg_dec_hard_reset(base); } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_reset); static void mtk_jpeg_dec_set_brz_factor(void __iomem *base, u8 yscale_w, u8 yscale_h, u8 uvscale_w, u8 uvscale_h) @@ -299,12 +330,14 @@ static void mtk_jpeg_dec_set_bs_write_ptr(void __iomem *base, u32 ptr) writel(ptr, base + JPGDEC_REG_FILE_BRP); } -static void mtk_jpeg_dec_set_bs_info(void __iomem *base, u32 addr, u32 size) +static void mtk_jpeg_dec_set_bs_info(void __iomem *base, u32 addr, u32 size, + u32 bitstream_size) { mtk_jpeg_verify_align(addr, 16, JPGDEC_REG_FILE_ADDR); mtk_jpeg_verify_align(size, 128, JPGDEC_REG_FILE_TOTAL_SIZE); writel(addr, base + JPGDEC_REG_FILE_ADDR); writel(size, base + JPGDEC_REG_FILE_TOTAL_SIZE); + writel(bitstream_size, base + JPGDEC_REG_BIT_STREAM_SIZE); } static void mtk_jpeg_dec_set_comp_id(void __iomem *base, u32 id_y, u32 id_u, @@ -373,37 +406,275 @@ static void mtk_jpeg_dec_set_sampling_factor(void __iomem *base, u32 comp_num, } void mtk_jpeg_dec_set_config(void __iomem *base, - struct mtk_jpeg_dec_param *config, + struct mtk_jpeg_dec_param *cfg, + u32 bitstream_size, struct mtk_jpeg_bs *bs, struct mtk_jpeg_fb *fb) { - mtk_jpeg_dec_set_brz_factor(base, 0, 0, config->uv_brz_w, 0); + mtk_jpeg_dec_set_brz_factor(base, 0, 0, cfg->uv_brz_w, 0); mtk_jpeg_dec_set_dec_mode(base, 0); - mtk_jpeg_dec_set_comp0_du(base, config->unit_num); - mtk_jpeg_dec_set_total_mcu(base, config->total_mcu); - mtk_jpeg_dec_set_bs_info(base, bs->str_addr, bs->size); + mtk_jpeg_dec_set_comp0_du(base, cfg->unit_num); + mtk_jpeg_dec_set_total_mcu(base, cfg->total_mcu); + mtk_jpeg_dec_set_bs_info(base, bs->str_addr, bs->size, bitstream_size); mtk_jpeg_dec_set_bs_write_ptr(base, bs->end_addr); - mtk_jpeg_dec_set_du_membership(base, config->membership, 1, - (config->comp_num == 1) ? 1 : 0); - mtk_jpeg_dec_set_comp_id(base, config->comp_id[0], config->comp_id[1], - config->comp_id[2]); - mtk_jpeg_dec_set_q_table(base, config->qtbl_num[0], - config->qtbl_num[1], config->qtbl_num[2]); - mtk_jpeg_dec_set_sampling_factor(base, config->comp_num, - config->sampling_w[0], - config->sampling_h[0], - config->sampling_w[1], - config->sampling_h[1], - config->sampling_w[2], - config->sampling_h[2]); - mtk_jpeg_dec_set_mem_stride(base, config->mem_stride[0], - config->mem_stride[1]); - mtk_jpeg_dec_set_img_stride(base, config->img_stride[0], - config->img_stride[1]); + mtk_jpeg_dec_set_du_membership(base, cfg->membership, 1, + (cfg->comp_num == 1) ? 1 : 0); + mtk_jpeg_dec_set_comp_id(base, cfg->comp_id[0], cfg->comp_id[1], + cfg->comp_id[2]); + mtk_jpeg_dec_set_q_table(base, cfg->qtbl_num[0], + cfg->qtbl_num[1], cfg->qtbl_num[2]); + mtk_jpeg_dec_set_sampling_factor(base, cfg->comp_num, + cfg->sampling_w[0], + cfg->sampling_h[0], + cfg->sampling_w[1], + cfg->sampling_h[1], + cfg->sampling_w[2], + cfg->sampling_h[2]); + mtk_jpeg_dec_set_mem_stride(base, cfg->mem_stride[0], + cfg->mem_stride[1]); + mtk_jpeg_dec_set_img_stride(base, cfg->img_stride[0], + cfg->img_stride[1]); mtk_jpeg_dec_set_dst_bank0(base, fb->plane_addr[0], fb->plane_addr[1], fb->plane_addr[2]); mtk_jpeg_dec_set_dst_bank1(base, 0, 0, 0); - mtk_jpeg_dec_set_dma_group(base, config->dma_mcu, config->dma_group, - config->dma_last_mcu); - mtk_jpeg_dec_set_pause_mcu_idx(base, config->total_mcu); + mtk_jpeg_dec_set_dma_group(base, cfg->dma_mcu, cfg->dma_group, + cfg->dma_last_mcu); + mtk_jpeg_dec_set_pause_mcu_idx(base, cfg->total_mcu); +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_set_config); + +static void mtk_jpegdec_put_buf(struct mtk_jpegdec_comp_dev *jpeg) +{ + struct mtk_jpeg_src_buf *dst_done_buf, *tmp_dst_done_buf; + struct vb2_v4l2_buffer *dst_buffer; + struct list_head *temp_entry; + struct list_head *pos = NULL; + struct mtk_jpeg_ctx *ctx; + unsigned long flags; + + ctx = jpeg->hw_param.curr_ctx; + if (unlikely(!ctx)) { + dev_err(jpeg->dev, "comp_jpeg ctx fail !!!\n"); + return; + } + + dst_buffer = jpeg->hw_param.dst_buffer; + if (!dst_buffer) { + dev_err(jpeg->dev, "comp_jpeg dst_buffer fail !!!\n"); + return; + } + + dst_done_buf = container_of(dst_buffer, struct mtk_jpeg_src_buf, b); + + spin_lock_irqsave(&ctx->done_queue_lock, flags); + list_add_tail(&dst_done_buf->list, &ctx->dst_done_queue); + while (!list_empty(&ctx->dst_done_queue) && + (pos != &ctx->dst_done_queue)) { + list_for_each_prev_safe(pos, temp_entry, &ctx->dst_done_queue) { + tmp_dst_done_buf = list_entry(pos, + struct mtk_jpeg_src_buf, + list); + if (tmp_dst_done_buf->frame_num == + ctx->last_done_frame_num) { + list_del(&tmp_dst_done_buf->list); + v4l2_m2m_buf_done(&tmp_dst_done_buf->b, + VB2_BUF_STATE_DONE); + ctx->last_done_frame_num++; + } + } + } + spin_unlock_irqrestore(&ctx->done_queue_lock, flags); +} + +static void mtk_jpegdec_timeout_work(struct work_struct *work) +{ + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct mtk_jpegdec_comp_dev *cjpeg = + container_of(work, struct mtk_jpegdec_comp_dev, + job_timeout_work.work); + struct mtk_jpeg_dev *master_jpeg = cjpeg->master_dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = cjpeg->hw_param.src_buffer; + dst_buf = cjpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + mtk_jpeg_dec_reset(cjpeg->reg_base); + clk_disable_unprepare(cjpeg->jdec_clk.clks->clk); + pm_runtime_put(cjpeg->dev); + cjpeg->hw_state = MTK_JPEG_HW_IDLE; + atomic_inc(&master_jpeg->dechw_rdy); + wake_up(&master_jpeg->dec_hw_wq); + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegdec_put_buf(cjpeg); +} + +static irqreturn_t mtk_jpegdec_hw_irq_handler(int irq, void *priv) +{ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mtk_jpeg_src_buf *jpeg_src_buf; + enum vb2_buffer_state buf_state; + struct mtk_jpeg_ctx *ctx; + u32 dec_irq_ret; + u32 irq_status; + int i; + + struct mtk_jpegdec_comp_dev *jpeg = priv; + struct mtk_jpeg_dev *master_jpeg = jpeg->master_dev; + + cancel_delayed_work(&jpeg->job_timeout_work); + + ctx = jpeg->hw_param.curr_ctx; + src_buf = jpeg->hw_param.src_buffer; + dst_buf = jpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + irq_status = mtk_jpeg_dec_get_int_status(jpeg->reg_base); + dec_irq_ret = mtk_jpeg_dec_enum_result(irq_status); + if (dec_irq_ret >= MTK_JPEG_DEC_RESULT_UNDERFLOW) + mtk_jpeg_dec_reset(jpeg->reg_base); + + if (dec_irq_ret != MTK_JPEG_DEC_RESULT_EOF_DONE) + dev_warn(jpeg->dev, "Jpg Dec occurs unknown Err."); + + jpeg_src_buf = + container_of(src_buf, struct mtk_jpeg_src_buf, b); + + for (i = 0; i < dst_buf->vb2_buf.num_planes; i++) + vb2_set_plane_payload(&dst_buf->vb2_buf, i, + jpeg_src_buf->dec_param.comp_size[i]); + + buf_state = VB2_BUF_STATE_DONE; + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegdec_put_buf(jpeg); + pm_runtime_put(ctx->jpeg->dev); + clk_disable_unprepare(jpeg->jdec_clk.clks->clk); + + jpeg->hw_state = MTK_JPEG_HW_IDLE; + wake_up(&master_jpeg->dec_hw_wq); + atomic_inc(&master_jpeg->dechw_rdy); + + return IRQ_HANDLED; +} + +static int mtk_jpegdec_hw_init_irq(struct mtk_jpegdec_comp_dev *dev) +{ + struct platform_device *pdev = dev->plat_dev; + int ret; + + dev->jpegdec_irq = platform_get_irq(pdev, 0); + if (dev->jpegdec_irq < 0) + return dev->jpegdec_irq; + + ret = devm_request_irq(&pdev->dev, + dev->jpegdec_irq, + mtk_jpegdec_hw_irq_handler, + 0, + pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "Failed to devm_request_irq %d (%d)", + dev->jpegdec_irq, ret); + return ret; + } + + return 0; } + +static void mtk_jpegdec_destroy_workqueue(void *data) +{ + destroy_workqueue(data); +} + +static int mtk_jpegdec_hw_probe(struct platform_device *pdev) +{ + struct mtk_jpegdec_clk *jpegdec_clk; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegdec_comp_dev *dev; + int ret, i; + + struct device *decs = &pdev->dev; + + if (!decs->parent) + return -EPROBE_DEFER; + + master_dev = dev_get_drvdata(decs->parent); + if (!master_dev) + return -EPROBE_DEFER; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->plat_dev = pdev; + dev->dev = &pdev->dev; + + if (!master_dev->is_jpgdec_multihw) { + master_dev->is_jpgdec_multihw = true; + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) + master_dev->dec_hw_dev[i] = NULL; + + init_waitqueue_head(&master_dev->dec_hw_wq); + master_dev->workqueue = alloc_ordered_workqueue(MTK_JPEG_NAME, + WQ_MEM_RECLAIM + | WQ_FREEZABLE); + if (!master_dev->workqueue) + return -EINVAL; + + ret = devm_add_action_or_reset(&pdev->dev, mtk_jpegdec_destroy_workqueue, + master_dev->workqueue); + if (ret) + return ret; + } + + atomic_set(&master_dev->dechw_rdy, MTK_JPEGDEC_HW_MAX); + spin_lock_init(&dev->hw_lock); + dev->hw_state = MTK_JPEG_HW_IDLE; + + INIT_DELAYED_WORK(&dev->job_timeout_work, + mtk_jpegdec_timeout_work); + + jpegdec_clk = &dev->jdec_clk; + + jpegdec_clk->clk_num = devm_clk_bulk_get_all(&pdev->dev, + &jpegdec_clk->clks); + if (jpegdec_clk->clk_num < 0) + return dev_err_probe(&pdev->dev, + jpegdec_clk->clk_num, + "Failed to get jpegdec clock count.\n"); + + dev->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->reg_base)) + return PTR_ERR(dev->reg_base); + + ret = mtk_jpegdec_hw_init_irq(dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register JPEGDEC irq handler.\n"); + + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) { + if (master_dev->dec_hw_dev[i]) + continue; + + master_dev->dec_hw_dev[i] = dev; + master_dev->reg_decbase[i] = dev->reg_base; + dev->master_dev = master_dev; + } + + platform_set_drvdata(pdev, dev); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static struct platform_driver mtk_jpegdec_hw_driver = { + .probe = mtk_jpegdec_hw_probe, + .driver = { + .name = "mtk-jpegdec-hw", + .of_match_table = of_match_ptr(mtk_jpegdec_hw_ids), + }, +}; + +module_platform_driver(mtk_jpegdec_hw_driver); + +MODULE_DESCRIPTION("MediaTek JPEG decode HW driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h index fa0d45fd7c34..8c31c6b12417 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h @@ -11,9 +11,10 @@ #include <media/videobuf2-core.h> -#include "mtk_jpeg_core.h" #include "mtk_jpeg_dec_reg.h" +#define MTK_JPEG_COMP_MAX 3 + enum { MTK_JPEG_DEC_RESULT_EOF_DONE = 0, MTK_JPEG_DEC_RESULT_PAUSE = 1, @@ -70,7 +71,8 @@ int mtk_jpeg_dec_fill_param(struct mtk_jpeg_dec_param *param); u32 mtk_jpeg_dec_get_int_status(void __iomem *dec_reg_base); u32 mtk_jpeg_dec_enum_result(u32 irq_result); void mtk_jpeg_dec_set_config(void __iomem *base, - struct mtk_jpeg_dec_param *config, + struct mtk_jpeg_dec_param *cfg, + u32 bitstream_size, struct mtk_jpeg_bs *bs, struct mtk_jpeg_fb *fb); void mtk_jpeg_dec_reset(void __iomem *dec_reg_base); diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h index 21ec8f96797f..27b7711ca341 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h @@ -45,5 +45,6 @@ #define JPGDEC_REG_QT_ID 0x0270 #define JPGDEC_REG_INTERRUPT_STATUS 0x0274 #define JPGDEC_REG_STATUS 0x0278 +#define JPGDEC_REG_BIT_STREAM_SIZE 0x0344 #endif /* _MTK_JPEG_REG_H */ diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c index 1cf037bf72dd..1bbb712d78d0 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c @@ -5,11 +5,27 @@ * */ +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/io.h> #include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <media/media-device.h> #include <media/videobuf2-core.h> #include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> +#include "mtk_jpeg_core.h" #include "mtk_jpeg_enc_hw.h" static const struct mtk_jpeg_enc_qlt mtk_jpeg_enc_quality[] = { @@ -30,18 +46,30 @@ static const struct mtk_jpeg_enc_qlt mtk_jpeg_enc_quality[] = { {.quality_param = 97, .hardware_value = JPEG_ENC_QUALITY_Q97}, }; +#if defined(CONFIG_OF) +static const struct of_device_id mtk_jpegenc_drv_ids[] = { + { + .compatible = "mediatek,mt8195-jpgenc-hw", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_jpegenc_drv_ids); +#endif + void mtk_jpeg_enc_reset(void __iomem *base) { writel(0, base + JPEG_ENC_RSTB); writel(JPEG_ENC_RESET_BIT, base + JPEG_ENC_RSTB); writel(0, base + JPEG_ENC_CODEC_SEL); } +EXPORT_SYMBOL_GPL(mtk_jpeg_enc_reset); u32 mtk_jpeg_enc_get_file_size(void __iomem *base) { return readl(base + JPEG_ENC_DMA_ADDR0) - readl(base + JPEG_ENC_DST_ADDR0); } +EXPORT_SYMBOL_GPL(mtk_jpeg_enc_get_file_size); void mtk_jpeg_enc_start(void __iomem *base) { @@ -51,6 +79,7 @@ void mtk_jpeg_enc_start(void __iomem *base) value |= JPEG_ENC_CTRL_INT_EN_BIT | JPEG_ENC_CTRL_ENABLE_BIT; writel(value, base + JPEG_ENC_CTRL); } +EXPORT_SYMBOL_GPL(mtk_jpeg_enc_start); void mtk_jpeg_set_enc_src(struct mtk_jpeg_ctx *ctx, void __iomem *base, struct vb2_buffer *src_buf) @@ -67,6 +96,7 @@ void mtk_jpeg_set_enc_src(struct mtk_jpeg_ctx *ctx, void __iomem *base, writel(dma_addr, base + JPEG_ENC_SRC_CHROMA_ADDR); } } +EXPORT_SYMBOL_GPL(mtk_jpeg_set_enc_src); void mtk_jpeg_set_enc_dst(struct mtk_jpeg_ctx *ctx, void __iomem *base, struct vb2_buffer *dst_buf) @@ -86,6 +116,7 @@ void mtk_jpeg_set_enc_dst(struct mtk_jpeg_ctx *ctx, void __iomem *base, writel(dma_addr & ~0xf, base + JPEG_ENC_DST_ADDR0); writel((dma_addr + size) & ~0xf, base + JPEG_ENC_STALL_ADDR0); } +EXPORT_SYMBOL_GPL(mtk_jpeg_set_enc_dst); void mtk_jpeg_set_enc_params(struct mtk_jpeg_ctx *ctx, void __iomem *base) { @@ -152,3 +183,227 @@ void mtk_jpeg_set_enc_params(struct mtk_jpeg_ctx *ctx, void __iomem *base) writel(ctx->restart_interval, base + JPEG_ENC_RST_MCU_NUM); } +EXPORT_SYMBOL_GPL(mtk_jpeg_set_enc_params); + +static void mtk_jpegenc_put_buf(struct mtk_jpegenc_comp_dev *jpeg) +{ + struct mtk_jpeg_ctx *ctx; + struct vb2_v4l2_buffer *dst_buffer; + struct list_head *temp_entry; + struct list_head *pos = NULL; + struct mtk_jpeg_src_buf *dst_done_buf, *tmp_dst_done_buf; + unsigned long flags; + + ctx = jpeg->hw_param.curr_ctx; + if (!ctx) { + dev_err(jpeg->dev, "comp_jpeg ctx fail !!!\n"); + return; + } + + dst_buffer = jpeg->hw_param.dst_buffer; + if (!dst_buffer) { + dev_err(jpeg->dev, "comp_jpeg dst_buffer fail !!!\n"); + return; + } + + dst_done_buf = container_of(dst_buffer, + struct mtk_jpeg_src_buf, b); + + spin_lock_irqsave(&ctx->done_queue_lock, flags); + list_add_tail(&dst_done_buf->list, &ctx->dst_done_queue); + while (!list_empty(&ctx->dst_done_queue) && + (pos != &ctx->dst_done_queue)) { + list_for_each_prev_safe(pos, temp_entry, &ctx->dst_done_queue) { + tmp_dst_done_buf = list_entry(pos, + struct mtk_jpeg_src_buf, + list); + if (tmp_dst_done_buf->frame_num == + ctx->last_done_frame_num) { + list_del(&tmp_dst_done_buf->list); + v4l2_m2m_buf_done(&tmp_dst_done_buf->b, + VB2_BUF_STATE_DONE); + ctx->last_done_frame_num++; + } + } + } + spin_unlock_irqrestore(&ctx->done_queue_lock, flags); +} + +static void mtk_jpegenc_timeout_work(struct work_struct *work) +{ + struct delayed_work *dly_work = to_delayed_work(work); + struct mtk_jpegenc_comp_dev *cjpeg = + container_of(dly_work, + struct mtk_jpegenc_comp_dev, + job_timeout_work); + struct mtk_jpeg_dev *master_jpeg = cjpeg->master_dev; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = cjpeg->hw_param.src_buffer; + dst_buf = cjpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + mtk_jpeg_enc_reset(cjpeg->reg_base); + clk_disable_unprepare(cjpeg->venc_clk.clks->clk); + pm_runtime_put(cjpeg->dev); + cjpeg->hw_state = MTK_JPEG_HW_IDLE; + atomic_inc(&master_jpeg->enchw_rdy); + wake_up(&master_jpeg->enc_hw_wq); + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegenc_put_buf(cjpeg); +} + +static irqreturn_t mtk_jpegenc_hw_irq_handler(int irq, void *priv) +{ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + enum vb2_buffer_state buf_state; + struct mtk_jpeg_ctx *ctx; + u32 result_size; + u32 irq_status; + + struct mtk_jpegenc_comp_dev *jpeg = priv; + struct mtk_jpeg_dev *master_jpeg = jpeg->master_dev; + + cancel_delayed_work(&jpeg->job_timeout_work); + + ctx = jpeg->hw_param.curr_ctx; + src_buf = jpeg->hw_param.src_buffer; + dst_buf = jpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + irq_status = readl(jpeg->reg_base + JPEG_ENC_INT_STS) & + JPEG_ENC_INT_STATUS_MASK_ALLIRQ; + if (irq_status) + writel(0, jpeg->reg_base + JPEG_ENC_INT_STS); + if (!(irq_status & JPEG_ENC_INT_STATUS_DONE)) + dev_warn(jpeg->dev, "Jpg Enc occurs unknown Err."); + + result_size = mtk_jpeg_enc_get_file_size(jpeg->reg_base); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, result_size); + buf_state = VB2_BUF_STATE_DONE; + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegenc_put_buf(jpeg); + pm_runtime_put(ctx->jpeg->dev); + clk_disable_unprepare(jpeg->venc_clk.clks->clk); + if (!list_empty(&ctx->fh.m2m_ctx->out_q_ctx.rdy_queue) || + !list_empty(&ctx->fh.m2m_ctx->cap_q_ctx.rdy_queue)) { + queue_work(master_jpeg->workqueue, &ctx->jpeg_work); + } + + jpeg->hw_state = MTK_JPEG_HW_IDLE; + wake_up(&master_jpeg->enc_hw_wq); + atomic_inc(&master_jpeg->enchw_rdy); + + return IRQ_HANDLED; +} + +static int mtk_jpegenc_hw_init_irq(struct mtk_jpegenc_comp_dev *dev) +{ + struct platform_device *pdev = dev->plat_dev; + int ret; + + dev->jpegenc_irq = platform_get_irq(pdev, 0); + if (dev->jpegenc_irq < 0) + return dev->jpegenc_irq; + + ret = devm_request_irq(&pdev->dev, + dev->jpegenc_irq, + mtk_jpegenc_hw_irq_handler, + 0, + pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "Failed to devm_request_irq %d (%d)", + dev->jpegenc_irq, ret); + return ret; + } + + return 0; +} + +static int mtk_jpegenc_hw_probe(struct platform_device *pdev) +{ + struct mtk_jpegenc_clk *jpegenc_clk; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegenc_comp_dev *dev; + int ret, i; + + struct device *decs = &pdev->dev; + + if (!decs->parent) + return -EPROBE_DEFER; + + master_dev = dev_get_drvdata(decs->parent); + if (!master_dev) + return -EPROBE_DEFER; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->plat_dev = pdev; + dev->dev = &pdev->dev; + + if (!master_dev->is_jpgenc_multihw) { + master_dev->is_jpgenc_multihw = true; + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) + master_dev->enc_hw_dev[i] = NULL; + + init_waitqueue_head(&master_dev->enc_hw_wq); + master_dev->workqueue = alloc_ordered_workqueue(MTK_JPEG_NAME, + WQ_MEM_RECLAIM + | WQ_FREEZABLE); + if (!master_dev->workqueue) + return -EINVAL; + } + + atomic_set(&master_dev->enchw_rdy, MTK_JPEGENC_HW_MAX); + spin_lock_init(&dev->hw_lock); + dev->hw_state = MTK_JPEG_HW_IDLE; + + INIT_DELAYED_WORK(&dev->job_timeout_work, + mtk_jpegenc_timeout_work); + + jpegenc_clk = &dev->venc_clk; + + jpegenc_clk->clk_num = devm_clk_bulk_get_all(&pdev->dev, + &jpegenc_clk->clks); + if (jpegenc_clk->clk_num < 0) + return dev_err_probe(&pdev->dev, jpegenc_clk->clk_num, + "Failed to get jpegenc clock count\n"); + + dev->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->reg_base)) + return PTR_ERR(dev->reg_base); + + ret = mtk_jpegenc_hw_init_irq(dev); + if (ret) + return ret; + + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) { + if (master_dev->enc_hw_dev[i]) + continue; + + master_dev->enc_hw_dev[i] = dev; + master_dev->reg_encbase[i] = dev->reg_base; + dev->master_dev = master_dev; + } + + platform_set_drvdata(pdev, dev); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static struct platform_driver mtk_jpegenc_hw_driver = { + .probe = mtk_jpegenc_hw_probe, + .driver = { + .name = "mtk-jpegenc-hw", + .of_match_table = of_match_ptr(mtk_jpegenc_drv_ids), + }, +}; + +module_platform_driver(mtk_jpegenc_hw_driver); + +MODULE_DESCRIPTION("MediaTek JPEG encode HW driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c index 1e3833f1c9ae..ad5fab2d8bfa 100644 --- a/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c @@ -52,9 +52,8 @@ int mtk_mdp_comp_init(struct device *dev, struct device_node *node, for (i = 0; i < ARRAY_SIZE(comp->clk); i++) { comp->clk[i] = of_clk_get(node, i); if (IS_ERR(comp->clk[i])) { - if (PTR_ERR(comp->clk[i]) != -EPROBE_DEFER) - dev_err(dev, "Failed to get clock\n"); - ret = PTR_ERR(comp->clk[i]); + ret = dev_err_probe(dev, PTR_ERR(comp->clk[i]), + "Failed to get clock\n"); goto put_dev; } diff --git a/drivers/media/platform/mediatek/mdp3/Kconfig b/drivers/media/platform/mediatek/mdp3/Kconfig index 50ae07b75b5f..846e759a8f6a 100644 --- a/drivers/media/platform/mediatek/mdp3/Kconfig +++ b/drivers/media/platform/mediatek/mdp3/Kconfig @@ -9,7 +9,6 @@ config VIDEO_MEDIATEK_MDP3 select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV select MTK_MMSYS - select VIDEO_MEDIATEK_VPU select MTK_CMDQ select MTK_SCP default n diff --git a/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h b/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h index 3e66ebaee2da..c7f231f8ea3e 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h +++ b/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h @@ -51,14 +51,14 @@ struct img_sw_addr { struct img_plane_format { u32 size; - u16 stride; + u32 stride; } __packed; struct img_pix_format { - u16 width; - u16 height; + u32 width; + u32 height; u32 colorformat; /* enum mdp_color */ - u16 ycbcr_prof; /* enum mdp_ycbcr_profile */ + u32 ycbcr_prof; /* enum mdp_ycbcr_profile */ struct img_plane_format plane_fmt[IMG_MAX_PLANES]; } __packed; @@ -72,10 +72,10 @@ struct img_image_buffer { #define IMG_SUBPIXEL_SHIFT 20 struct img_crop { - s16 left; - s16 top; - u16 width; - u16 height; + s32 left; + s32 top; + u32 width; + u32 height; u32 left_subpix; u32 top_subpix; u32 width_subpix; @@ -90,24 +90,24 @@ struct img_crop { struct img_input { struct img_image_buffer buffer; - u16 flags; /* HDR, DRE, dither */ + u32 flags; /* HDR, DRE, dither */ } __packed; struct img_output { struct img_image_buffer buffer; struct img_crop crop; - s16 rotation; - u16 flags; /* H-flip, sharpness, dither */ + s32 rotation; + u32 flags; /* H-flip, sharpness, dither */ } __packed; struct img_ipi_frameparam { u32 index; u32 frame_no; struct img_timeval timestamp; - u8 type; /* enum mdp_stream_type */ - u8 state; - u8 num_inputs; - u8 num_outputs; + u32 type; /* enum mdp_stream_type */ + u32 state; + u32 num_inputs; + u32 num_outputs; u64 drv_data; struct img_input inputs[IMG_MAX_HW_INPUTS]; struct img_output outputs[IMG_MAX_HW_OUTPUTS]; @@ -123,51 +123,51 @@ struct img_sw_buffer { } __packed; struct img_ipi_param { - u8 usage; + u32 usage; struct img_sw_buffer frm_param; } __packed; struct img_frameparam { struct list_head list_entry; struct img_ipi_frameparam frameparam; -}; +} __packed; /* ISP-MDP generic output information */ struct img_comp_frame { - u32 output_disable:1; - u32 bypass:1; - u16 in_width; - u16 in_height; - u16 out_width; - u16 out_height; + u32 output_disable; + u32 bypass; + u32 in_width; + u32 in_height; + u32 out_width; + u32 out_height; struct img_crop crop; - u16 in_total_width; - u16 out_total_width; + u32 in_total_width; + u32 out_total_width; } __packed; struct img_region { - s16 left; - s16 right; - s16 top; - s16 bottom; + s32 left; + s32 right; + s32 top; + s32 bottom; } __packed; struct img_offset { - s16 left; - s16 top; + s32 left; + s32 top; u32 left_subpix; u32 top_subpix; } __packed; struct img_comp_subfrm { - u32 tile_disable:1; + u32 tile_disable; struct img_region in; struct img_region out; struct img_offset luma; struct img_offset chroma; - s16 out_vertical; /* Output vertical index */ - s16 out_horizontal; /* Output horizontal index */ + s32 out_vertical; /* Output vertical index */ + s32 out_horizontal; /* Output horizontal index */ } __packed; #define IMG_MAX_SUBFRAMES 14 @@ -250,8 +250,8 @@ struct isp_data { } __packed; struct img_compparam { - u16 type; /* enum mdp_comp_type */ - u16 id; /* enum mtk_mdp_comp_id */ + u32 type; /* enum mdp_comp_id */ + u32 id; /* engine alias_id */ u32 input; u32 outputs[IMG_MAX_HW_OUTPUTS]; u32 num_outputs; @@ -273,12 +273,12 @@ struct img_mux { u32 reg; u32 value; u32 subsys_id; -}; +} __packed; struct img_mmsys_ctrl { struct img_mux sets[IMG_MAX_COMPONENTS * 2]; u32 num_sets; -}; +} __packed; struct img_config { struct img_compparam components[IMG_MAX_COMPONENTS]; diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c index 86c054600a08..124c1b96e96b 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c @@ -252,10 +252,9 @@ static int mdp_cmdq_pkt_create(struct cmdq_client *client, struct cmdq_pkt *pkt, dma_addr_t dma_addr; pkt->va_base = kzalloc(size, GFP_KERNEL); - if (!pkt->va_base) { - kfree(pkt); + if (!pkt->va_base) return -ENOMEM; - } + pkt->buf_size = size; pkt->cl = (void *)client; @@ -368,25 +367,30 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (!cmd) { ret = -ENOMEM; - goto err_cmdq_data; + goto err_cancel_job; } - if (mdp_cmdq_pkt_create(mdp->cmdq_clt, &cmd->pkt, SZ_16K)) { - ret = -ENOMEM; - goto err_cmdq_data; - } + ret = mdp_cmdq_pkt_create(mdp->cmdq_clt, &cmd->pkt, SZ_16K); + if (ret) + goto err_free_cmd; comps = kcalloc(param->config->num_components, sizeof(*comps), GFP_KERNEL); if (!comps) { ret = -ENOMEM; - goto err_cmdq_data; + goto err_destroy_pkt; } path = kzalloc(sizeof(*path), GFP_KERNEL); if (!path) { ret = -ENOMEM; - goto err_cmdq_data; + goto err_free_comps; + } + + ret = mtk_mutex_prepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); + if (ret) { + dev_err(dev, "Fail to enable mutex clk\n"); + goto err_free_path; } path->mdp_dev = mdp; @@ -406,15 +410,13 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) ret = mdp_path_ctx_init(mdp, path); if (ret) { dev_err(dev, "mdp_path_ctx_init error\n"); - goto err_cmdq_data; + goto err_free_path; } - mtk_mutex_prepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); - ret = mdp_path_config(mdp, cmd, path); if (ret) { dev_err(dev, "mdp_path_config error\n"); - goto err_cmdq_data; + goto err_free_path; } cmdq_pkt_finalize(&cmd->pkt); @@ -431,10 +433,8 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) cmd->mdp_ctx = param->mdp_ctx; ret = mdp_comp_clocks_on(&mdp->pdev->dev, cmd->comps, cmd->num_comps); - if (ret) { - dev_err(dev, "comp %d failed to enable clock!\n", ret); - goto err_clock_off; - } + if (ret) + goto err_free_path; dma_sync_single_for_device(mdp->cmdq_clt->chan->mbox->dev, cmd->pkt.pa_base, cmd->pkt.cmd_buf_size, @@ -450,17 +450,20 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) return 0; err_clock_off: - mtk_mutex_unprepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); mdp_comp_clocks_off(&mdp->pdev->dev, cmd->comps, cmd->num_comps); -err_cmdq_data: +err_free_path: + mtk_mutex_unprepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); kfree(path); - atomic_dec(&mdp->job_count); - wake_up(&mdp->callback_wq); - if (cmd && cmd->pkt.buf_size > 0) - mdp_cmdq_pkt_destroy(&cmd->pkt); +err_free_comps: kfree(comps); +err_destroy_pkt: + mdp_cmdq_pkt_destroy(&cmd->pkt); +err_free_cmd: kfree(cmd); +err_cancel_job: + atomic_dec(&mdp->job_count); + return ret; } EXPORT_SYMBOL_GPL(mdp_cmdq_send); diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c index d3eaf8884412..7bc05f42a23c 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c @@ -699,12 +699,22 @@ int mdp_comp_clock_on(struct device *dev, struct mdp_comp *comp) dev_err(dev, "Failed to enable clk %d. type:%d id:%d\n", i, comp->type, comp->id); - pm_runtime_put(comp->comp_dev); - return ret; + goto err_revert; } } return 0; + +err_revert: + while (--i >= 0) { + if (IS_ERR_OR_NULL(comp->clks[i])) + continue; + clk_disable_unprepare(comp->clks[i]); + } + if (comp->comp_dev) + pm_runtime_put_sync(comp->comp_dev); + + return ret; } void mdp_comp_clock_off(struct device *dev, struct mdp_comp *comp) @@ -723,11 +733,13 @@ void mdp_comp_clock_off(struct device *dev, struct mdp_comp *comp) int mdp_comp_clocks_on(struct device *dev, struct mdp_comp *comps, int num) { - int i; + int i, ret; - for (i = 0; i < num; i++) - if (mdp_comp_clock_on(dev, &comps[i]) != 0) - return ++i; + for (i = 0; i < num; i++) { + ret = mdp_comp_clock_on(dev, &comps[i]); + if (ret) + return ret; + } return 0; } diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c index c413e59d4286..2d1f6ae9f080 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c @@ -196,27 +196,27 @@ static int mdp_probe(struct platform_device *pdev) mm_pdev = __get_pdev_by_id(pdev, MDP_INFRA_MMSYS); if (!mm_pdev) { ret = -ENODEV; - goto err_return; + goto err_destroy_device; } mdp->mdp_mmsys = &mm_pdev->dev; mm_pdev = __get_pdev_by_id(pdev, MDP_INFRA_MUTEX); if (WARN_ON(!mm_pdev)) { ret = -ENODEV; - goto err_return; + goto err_destroy_device; } for (i = 0; i < MDP_PIPE_MAX; i++) { mdp->mdp_mutex[i] = mtk_mutex_get(&mm_pdev->dev); if (!mdp->mdp_mutex[i]) { ret = -ENODEV; - goto err_return; + goto err_free_mutex; } } ret = mdp_comp_config(mdp); if (ret) { dev_err(dev, "Failed to config mdp components\n"); - goto err_return; + goto err_free_mutex; } mdp->job_wq = alloc_workqueue(MDP_MODULE_NAME, WQ_FREEZABLE, 0); @@ -287,11 +287,12 @@ err_destroy_job_wq: destroy_workqueue(mdp->job_wq); err_deinit_comp: mdp_comp_destroy(mdp); -err_return: +err_free_mutex: for (i = 0; i < MDP_PIPE_MAX; i++) - if (mdp) - mtk_mutex_put(mdp->mdp_mutex[i]); + mtk_mutex_put(mdp->mdp_mutex[i]); +err_destroy_device: kfree(mdp); +err_return: dev_dbg(dev, "Errno %d\n", ret); return ret; } diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c index c45bd2599bb2..ffbcee04dc26 100644 --- a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c @@ -138,10 +138,13 @@ static void mtk_vdec_stateless_cap_to_disp(struct mtk_vcodec_ctx *ctx, int error state = VB2_BUF_STATE_DONE; vb2_dst = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); - v4l2_m2m_buf_done(vb2_dst, state); - - mtk_v4l2_debug(2, "free frame buffer id:%d to done list", - vb2_dst->vb2_buf.index); + if (vb2_dst) { + v4l2_m2m_buf_done(vb2_dst, state); + mtk_v4l2_debug(2, "free frame buffer id:%d to done list", + vb2_dst->vb2_buf.index); + } else { + mtk_v4l2_err("dst buffer is NULL"); + } if (src_buf_req) v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); @@ -250,7 +253,7 @@ static void mtk_vdec_worker(struct work_struct *work) state = ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE; if (!IS_VDEC_LAT_ARCH(dev->vdec_pdata->hw_arch) || - ctx->current_codec == V4L2_PIX_FMT_VP8_FRAME || ret) { + ctx->current_codec == V4L2_PIX_FMT_VP8_FRAME) { v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, state); if (src_buf_req) v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c index d810a78dde51..d65800a3b89d 100644 --- a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c @@ -1397,7 +1397,10 @@ int mtk_vcodec_enc_ctrls_setup(struct mtk_vcodec_ctx *ctx) 0, V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE); v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, - 0, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_H264_LEVEL, h264_max_level, 0, V4L2_MPEG_VIDEO_H264_LEVEL_4_0); diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c index 4cc92700692b..955b2d0c8f53 100644 --- a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c @@ -471,14 +471,19 @@ static int vdec_h264_slice_core_decode(struct vdec_lat_buf *lat_buf) sizeof(share_info->h264_slice_params)); fb = ctx->dev->vdec_pdata->get_cap_buffer(ctx); - y_fb_dma = fb ? (u64)fb->base_y.dma_addr : 0; - vdec_fb_va = (unsigned long)fb; + if (!fb) { + err = -EBUSY; + mtk_vcodec_err(inst, "fb buffer is NULL"); + goto vdec_dec_end; + } + vdec_fb_va = (unsigned long)fb; + y_fb_dma = (u64)fb->base_y.dma_addr; if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 1) c_fb_dma = y_fb_dma + inst->ctx->picinfo.buf_w * inst->ctx->picinfo.buf_h; else - c_fb_dma = fb ? (u64)fb->base_c.dma_addr : 0; + c_fb_dma = (u64)fb->base_c.dma_addr; mtk_vcodec_debug(inst, "[h264-core] y/c addr = 0x%llx 0x%llx", y_fb_dma, c_fb_dma); @@ -539,6 +544,29 @@ vdec_dec_end: return 0; } +static void vdec_h264_insert_startcode(struct mtk_vcodec_dev *vcodec_dev, unsigned char *buf, + size_t *bs_size, struct mtk_h264_pps_param *pps) +{ + struct device *dev = &vcodec_dev->plat_dev->dev; + + /* Need to add pending data at the end of bitstream when bs_sz is small than + * 20 bytes for cavlc bitstream, or lat will decode fail. This pending data is + * useful for mt8192 and mt8195 platform. + * + * cavlc bitstream when entropy_coding_mode_flag is false. + */ + if (pps->entropy_coding_mode_flag || *bs_size > 20 || + !(of_device_is_compatible(dev->of_node, "mediatek,mt8192-vcodec-dec") || + of_device_is_compatible(dev->of_node, "mediatek,mt8195-vcodec-dec"))) + return; + + buf[*bs_size] = 0; + buf[*bs_size + 1] = 0; + buf[*bs_size + 2] = 1; + buf[*bs_size + 3] = 0xff; + (*bs_size) += 4; +} + static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, struct vdec_fb *fb, bool *res_chg) { @@ -582,9 +610,6 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, } inst->vsi->dec.nal_info = buf[nal_start_idx]; - inst->vsi->dec.bs_buf_addr = (u64)bs->dma_addr; - inst->vsi->dec.bs_buf_size = bs->size; - lat_buf->src_buf_req = src_buf_info->m2m_buf.vb.vb2_buf.req_obj.req; v4l2_m2m_buf_copy_metadata(&src_buf_info->m2m_buf.vb, &lat_buf->ts_info, true); @@ -592,6 +617,12 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, if (err) goto err_free_fb_out; + vdec_h264_insert_startcode(inst->ctx->dev, buf, &bs->size, + &share_info->h264_slice_params.pps); + + inst->vsi->dec.bs_buf_addr = (uint64_t)bs->dma_addr; + inst->vsi->dec.bs_buf_size = bs->size; + *res_chg = inst->resolution_changed; if (inst->resolution_changed) { mtk_vcodec_debug(inst, "- resolution changed -"); @@ -630,7 +661,7 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, err = vpu_dec_start(vpu, data, 2); if (err) { mtk_vcodec_debug(inst, "lat decode err: %d", err); - goto err_scp_decode; + goto err_free_fb_out; } share_info->trans_end = inst->ctx->msg_queue.wdma_addr.dma_addr + @@ -647,12 +678,17 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, /* wait decoder done interrupt */ timeout = mtk_vcodec_wait_for_done_ctx(inst->ctx, MTK_INST_IRQ_RECEIVED, WAIT_INTR_TIMEOUT_MS, MTK_VDEC_LAT0); + if (timeout) + mtk_vcodec_err(inst, "lat decode timeout: pic_%d", inst->slice_dec_num); inst->vsi->dec.timeout = !!timeout; err = vpu_dec_end(vpu); - if (err == SLICE_HEADER_FULL || timeout || err == TRANS_BUFFER_FULL) { - err = -EINVAL; - goto err_scp_decode; + if (err == SLICE_HEADER_FULL || err == TRANS_BUFFER_FULL) { + if (!IS_VDEC_INNER_RACING(inst->ctx->dev->dec_capability)) + vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); + inst->slice_dec_num++; + mtk_vcodec_err(inst, "lat dec fail: pic_%d err:%d", inst->slice_dec_num, err); + return -EINVAL; } share_info->trans_end = inst->ctx->msg_queue.wdma_addr.dma_addr + @@ -669,10 +705,6 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, inst->slice_dec_num++; return 0; - -err_scp_decode: - if (!IS_VDEC_INNER_RACING(inst->ctx->dev->dec_capability)) - vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); err_free_fb_out: vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); mtk_vcodec_err(inst, "slice dec number: %d err: %d", inst->slice_dec_num, err); diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c index fb1c36a3592d..cbb6728b8a40 100644 --- a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c @@ -2073,21 +2073,23 @@ static int vdec_vp9_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, return -EBUSY; } pfc = (struct vdec_vp9_slice_pfc *)lat_buf->private_data; - if (!pfc) - return -EINVAL; + if (!pfc) { + ret = -EINVAL; + goto err_free_fb_out; + } vsi = &pfc->vsi; ret = vdec_vp9_slice_setup_lat(instance, bs, lat_buf, pfc); if (ret) { mtk_vcodec_err(instance, "Failed to setup VP9 lat ret %d\n", ret); - return ret; + goto err_free_fb_out; } vdec_vp9_slice_vsi_to_remote(vsi, instance->vsi); ret = vpu_dec_start(&instance->vpu, NULL, 0); if (ret) { mtk_vcodec_err(instance, "Failed to dec VP9 ret %d\n", ret); - return ret; + goto err_free_fb_out; } if (instance->irq) { @@ -2107,7 +2109,7 @@ static int vdec_vp9_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, /* LAT trans full, no more UBE or decode timeout */ if (ret) { mtk_vcodec_err(instance, "VP9 decode error: %d\n", ret); - return ret; + goto err_free_fb_out; } mtk_vcodec_debug(instance, "lat dma addr: 0x%lx 0x%lx\n", @@ -2120,6 +2122,9 @@ static int vdec_vp9_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, vdec_msg_queue_qbuf(&ctx->dev->msg_queue_core_ctx, lat_buf); return 0; +err_free_fb_out: + vdec_msg_queue_qbuf(&ctx->msg_queue.lat_ctx, lat_buf); + return ret; } static int vdec_vp9_slice_decode(void *h_vdec, struct mtk_vcodec_mem *bs, diff --git a/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c index ae500980ad45..dc2004790a47 100644 --- a/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c +++ b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c @@ -221,7 +221,7 @@ static void vdec_msg_queue_core_work(struct work_struct *work) mtk_vcodec_dec_disable_hardware(ctx, MTK_VDEC_CORE); vdec_msg_queue_qbuf(&ctx->msg_queue.lat_ctx, lat_buf); - if (!list_empty(&ctx->msg_queue.lat_ctx.ready_queue)) { + if (!list_empty(&dev->msg_queue_core_ctx.ready_queue)) { mtk_v4l2_debug(3, "re-schedule to decode for core: %d", dev->msg_queue_core_ctx.ready_num); queue_work(dev->core_workqueue, &msg_queue->core_work); diff --git a/drivers/media/platform/microchip/Kconfig b/drivers/media/platform/microchip/Kconfig new file mode 100644 index 000000000000..4734ecced029 --- /dev/null +++ b/drivers/media/platform/microchip/Kconfig @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Microchip Technology, Inc. media platform drivers" + +config VIDEO_MICROCHIP_ISC + tristate "Microchip Image Sensor Controller (ISC) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_MICROCHIP_ISC_BASE + help + This module makes the Microchip Image Sensor Controller available + as a v4l2 device. + + To compile this driver as a module, choose M here: the + module will be called microchip-isc. + +config VIDEO_MICROCHIP_XISC + tristate "Microchip eXtended Image Sensor Controller (XISC) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_MICROCHIP_ISC_BASE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This module makes the Microchip eXtended Image Sensor Controller + available as a v4l2 device. + + To compile this driver as a module, choose M here: the + module will be called microchip-xisc. + +config VIDEO_MICROCHIP_ISC_BASE + tristate + default n + help + Microchip ISC and XISC common code base. + +config VIDEO_MICROCHIP_CSI2DC + tristate "Microchip CSI2 Demux Controller" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK && OF + depends on ARCH_AT91 || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + CSI2 Demux Controller driver. CSI2DC is a helper chip + that converts IDI interface byte stream to a parallel pixel stream. + It supports various RAW formats as input. + + To compile this driver as a module, choose M here: the + module will be called microchip-csi2dc. diff --git a/drivers/media/platform/microchip/Makefile b/drivers/media/platform/microchip/Makefile new file mode 100644 index 000000000000..bd8d6e779c51 --- /dev/null +++ b/drivers/media/platform/microchip/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +microchip-isc-objs = microchip-sama5d2-isc.o +microchip-xisc-objs = microchip-sama7g5-isc.o +microchip-isc-common-objs = microchip-isc-base.o microchip-isc-clk.o microchip-isc-scaler.o + +obj-$(CONFIG_VIDEO_MICROCHIP_ISC_BASE) += microchip-isc-common.o +obj-$(CONFIG_VIDEO_MICROCHIP_ISC) += microchip-isc.o +obj-$(CONFIG_VIDEO_MICROCHIP_XISC) += microchip-xisc.o +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/microchip/microchip-csi2dc.c index d5b359f607ae..d5b359f607ae 100644 --- a/drivers/media/platform/atmel/microchip-csi2dc.c +++ b/drivers/media/platform/microchip/microchip-csi2dc.c diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c new file mode 100644 index 000000000000..e2994d48f10c --- /dev/null +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -0,0 +1,2040 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip Image Sensor Controller (ISC) common driver base + * + * Copyright (C) 2016-2019 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev <eugen.hristev@microchip.com> + * + */ +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/videodev2.h> +#include <linux/atmel-isc-media.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> + +#include "microchip-isc-regs.h" +#include "microchip-isc.h" + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +#define ISC_IS_FORMAT_RAW(mbus_code) \ + (((mbus_code) & 0xf000) == 0x3000) + +#define ISC_IS_FORMAT_GREY(mbus_code) \ + (((mbus_code) == MEDIA_BUS_FMT_Y10_1X10) | \ + (((mbus_code) == MEDIA_BUS_FMT_Y8_1X8))) + +static inline void isc_update_v4l2_ctrls(struct isc_device *isc) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + + /* In here we set the v4l2 controls w.r.t. our pipeline config */ + v4l2_ctrl_s_ctrl(isc->r_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_R]); + v4l2_ctrl_s_ctrl(isc->b_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_B]); + v4l2_ctrl_s_ctrl(isc->gr_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_GR]); + v4l2_ctrl_s_ctrl(isc->gb_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_GB]); + + v4l2_ctrl_s_ctrl(isc->r_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_R]); + v4l2_ctrl_s_ctrl(isc->b_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_B]); + v4l2_ctrl_s_ctrl(isc->gr_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_GR]); + v4l2_ctrl_s_ctrl(isc->gb_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_GB]); +} + +static inline void isc_update_awb_ctrls(struct isc_device *isc) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + + /* In here we set our actual hw pipeline config */ + + regmap_write(isc->regmap, ISC_WB_O_RGR, + ((ctrls->offset[ISC_HIS_CFG_MODE_R])) | + ((ctrls->offset[ISC_HIS_CFG_MODE_GR]) << 16)); + regmap_write(isc->regmap, ISC_WB_O_BGB, + ((ctrls->offset[ISC_HIS_CFG_MODE_B])) | + ((ctrls->offset[ISC_HIS_CFG_MODE_GB]) << 16)); + regmap_write(isc->regmap, ISC_WB_G_RGR, + ctrls->gain[ISC_HIS_CFG_MODE_R] | + (ctrls->gain[ISC_HIS_CFG_MODE_GR] << 16)); + regmap_write(isc->regmap, ISC_WB_G_BGB, + ctrls->gain[ISC_HIS_CFG_MODE_B] | + (ctrls->gain[ISC_HIS_CFG_MODE_GB] << 16)); +} + +static inline void isc_reset_awb_ctrls(struct isc_device *isc) +{ + unsigned int c; + + for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) { + /* gains have a fixed point at 9 decimals */ + isc->ctrls.gain[c] = 1 << 9; + /* offsets are in 2's complements */ + isc->ctrls.offset[c] = 0; + } +} + +static int isc_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + unsigned int size = isc->fmt.fmt.pix.sizeimage; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int isc_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct isc_device *isc = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = isc->fmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + v4l2_err(&isc->v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + vbuf->field = isc->fmt.fmt.pix.field; + + return 0; +} + +static void isc_crop_pfe(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 h, w; + + h = isc->fmt.fmt.pix.height; + w = isc->fmt.fmt.pix.width; + + /* + * In case the sensor is not RAW, it will output a pixel (12-16 bits) + * with two samples on the ISC Data bus (which is 8-12) + * ISC will count each sample, so, we need to multiply these values + * by two, to get the real number of samples for the required pixels. + */ + if (!ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) { + h <<= 1; + w <<= 1; + } + + /* + * We limit the column/row count that the ISC will output according + * to the configured resolution that we want. + * This will avoid the situation where the sensor is misconfigured, + * sending more data, and the ISC will just take it and DMA to memory, + * causing corruption. + */ + regmap_write(regmap, ISC_PFE_CFG1, + (ISC_PFE_CFG1_COLMIN(0) & ISC_PFE_CFG1_COLMIN_MASK) | + (ISC_PFE_CFG1_COLMAX(w - 1) & ISC_PFE_CFG1_COLMAX_MASK)); + + regmap_write(regmap, ISC_PFE_CFG2, + (ISC_PFE_CFG2_ROWMIN(0) & ISC_PFE_CFG2_ROWMIN_MASK) | + (ISC_PFE_CFG2_ROWMAX(h - 1) & ISC_PFE_CFG2_ROWMAX_MASK)); + + regmap_update_bits(regmap, ISC_PFE_CFG0, + ISC_PFE_CFG0_COLEN | ISC_PFE_CFG0_ROWEN, + ISC_PFE_CFG0_COLEN | ISC_PFE_CFG0_ROWEN); +} + +static void isc_start_dma(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 sizeimage = isc->fmt.fmt.pix.sizeimage; + u32 dctrl_dview; + dma_addr_t addr0; + + addr0 = vb2_dma_contig_plane_dma_addr(&isc->cur_frm->vb.vb2_buf, 0); + regmap_write(regmap, ISC_DAD0 + isc->offsets.dma, addr0); + + switch (isc->config.fourcc) { + case V4L2_PIX_FMT_YUV420: + regmap_write(regmap, ISC_DAD1 + isc->offsets.dma, + addr0 + (sizeimage * 2) / 3); + regmap_write(regmap, ISC_DAD2 + isc->offsets.dma, + addr0 + (sizeimage * 5) / 6); + break; + case V4L2_PIX_FMT_YUV422P: + regmap_write(regmap, ISC_DAD1 + isc->offsets.dma, + addr0 + sizeimage / 2); + regmap_write(regmap, ISC_DAD2 + isc->offsets.dma, + addr0 + (sizeimage * 3) / 4); + break; + default: + break; + } + + dctrl_dview = isc->config.dctrl_dview; + + regmap_write(regmap, ISC_DCTRL + isc->offsets.dma, + dctrl_dview | ISC_DCTRL_IE_IS); + spin_lock(&isc->awb_lock); + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_CAPTURE); + spin_unlock(&isc->awb_lock); +} + +static void isc_set_pipeline(struct isc_device *isc, u32 pipeline) +{ + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + u32 val, bay_cfg; + const u32 *gamma; + unsigned int i; + + /* WB-->CFA-->CC-->GAM-->CSC-->CBC-->SUB422-->SUB420 */ + for (i = 0; i < ISC_PIPE_LINE_NODE_NUM; i++) { + val = pipeline & BIT(i) ? 1 : 0; + regmap_field_write(isc->pipeline[i], val); + } + + if (!pipeline) + return; + + bay_cfg = isc->config.sd_format->cfa_baycfg; + + regmap_write(regmap, ISC_WB_CFG, bay_cfg); + isc_update_awb_ctrls(isc); + isc_update_v4l2_ctrls(isc); + + regmap_write(regmap, ISC_CFA_CFG, bay_cfg | ISC_CFA_CFG_EITPOL); + + gamma = &isc->gamma_table[ctrls->gamma_index][0]; + regmap_bulk_write(regmap, ISC_GAM_BENTRY, gamma, GAMMA_ENTRIES); + regmap_bulk_write(regmap, ISC_GAM_GENTRY, gamma, GAMMA_ENTRIES); + regmap_bulk_write(regmap, ISC_GAM_RENTRY, gamma, GAMMA_ENTRIES); + + isc->config_dpc(isc); + isc->config_csc(isc); + isc->config_cbc(isc); + isc->config_cc(isc); + isc->config_gam(isc); +} + +static int isc_update_profile(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 sr; + int counter = 100; + + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_UPPRO); + + regmap_read(regmap, ISC_CTRLSR, &sr); + while ((sr & ISC_CTRL_UPPRO) && counter--) { + usleep_range(1000, 2000); + regmap_read(regmap, ISC_CTRLSR, &sr); + } + + if (counter < 0) { + v4l2_warn(&isc->v4l2_dev, "Time out to update profile\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void isc_set_histogram(struct isc_device *isc, bool enable) +{ + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + + if (enable) { + regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his, + ISC_HIS_CFG_MODE_GR | + (isc->config.sd_format->cfa_baycfg + << ISC_HIS_CFG_BAYSEL_SHIFT) | + ISC_HIS_CFG_RAR); + regmap_write(regmap, ISC_HIS_CTRL + isc->offsets.his, + ISC_HIS_CTRL_EN); + regmap_write(regmap, ISC_INTEN, ISC_INT_HISDONE); + ctrls->hist_id = ISC_HIS_CFG_MODE_GR; + isc_update_profile(isc); + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); + + ctrls->hist_stat = HIST_ENABLED; + } else { + regmap_write(regmap, ISC_INTDIS, ISC_INT_HISDONE); + regmap_write(regmap, ISC_HIS_CTRL + isc->offsets.his, + ISC_HIS_CTRL_DIS); + + ctrls->hist_stat = HIST_DISABLED; + } +} + +static int isc_configure(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 pfe_cfg0, dcfg, mask, pipeline; + struct isc_subdev_entity *subdev = isc->current_subdev; + + pfe_cfg0 = isc->config.sd_format->pfe_cfg0_bps; + pipeline = isc->config.bits_pipeline; + + dcfg = isc->config.dcfg_imode | isc->dcfg; + + pfe_cfg0 |= subdev->pfe_cfg0 | ISC_PFE_CFG0_MODE_PROGRESSIVE; + mask = ISC_PFE_CFG0_BPS_MASK | ISC_PFE_CFG0_HPOL_LOW | + ISC_PFE_CFG0_VPOL_LOW | ISC_PFE_CFG0_PPOL_LOW | + ISC_PFE_CFG0_MODE_MASK | ISC_PFE_CFG0_CCIR_CRC | + ISC_PFE_CFG0_CCIR656 | ISC_PFE_CFG0_MIPI; + + regmap_update_bits(regmap, ISC_PFE_CFG0, mask, pfe_cfg0); + + isc->config_rlp(isc); + + regmap_write(regmap, ISC_DCFG + isc->offsets.dma, dcfg); + + /* Set the pipeline */ + isc_set_pipeline(isc, pipeline); + + /* + * The current implemented histogram is available for RAW R, B, GB, GR + * channels. We need to check if sensor is outputting RAW BAYER + */ + if (isc->ctrls.awb && + ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) + isc_set_histogram(isc, true); + else + isc_set_histogram(isc, false); + + /* Update profile */ + return isc_update_profile(isc); +} + +static int isc_prepare_streaming(struct vb2_queue *vq) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + + return media_pipeline_start(isc->video_dev.entity.pads, &isc->mpipe); +} + +static int isc_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + struct regmap *regmap = isc->regmap; + struct isc_buffer *buf; + unsigned long flags; + int ret; + + /* Enable stream on the sub device */ + ret = v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) { + v4l2_err(&isc->v4l2_dev, "stream on failed in subdev %d\n", + ret); + goto err_start_stream; + } + + ret = pm_runtime_resume_and_get(isc->dev); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, "RPM resume failed in subdev %d\n", + ret); + goto err_pm_get; + } + + ret = isc_configure(isc); + if (unlikely(ret)) + goto err_configure; + + /* Enable DMA interrupt */ + regmap_write(regmap, ISC_INTEN, ISC_INT_DDONE); + + spin_lock_irqsave(&isc->dma_queue_lock, flags); + + isc->sequence = 0; + isc->stop = false; + reinit_completion(&isc->comp); + + isc->cur_frm = list_first_entry(&isc->dma_queue, + struct isc_buffer, list); + list_del(&isc->cur_frm->list); + + isc_crop_pfe(isc); + isc_start_dma(isc); + + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); + + /* if we streaming from RAW, we can do one-shot white balance adj */ + if (ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) + v4l2_ctrl_activate(isc->do_wb_ctrl, true); + + return 0; + +err_configure: + pm_runtime_put_sync(isc->dev); +err_pm_get: + v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 0); + +err_start_stream: + spin_lock_irqsave(&isc->dma_queue_lock, flags); + list_for_each_entry(buf, &isc->dma_queue, list) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + INIT_LIST_HEAD(&isc->dma_queue); + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); + + return ret; +} + +static void isc_unprepare_streaming(struct vb2_queue *vq) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + + /* Stop media pipeline */ + media_pipeline_stop(isc->video_dev.entity.pads); +} + +static void isc_stop_streaming(struct vb2_queue *vq) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + unsigned long flags; + struct isc_buffer *buf; + int ret; + + mutex_lock(&isc->awb_mutex); + v4l2_ctrl_activate(isc->do_wb_ctrl, false); + + isc->stop = true; + + /* Wait until the end of the current frame */ + if (isc->cur_frm && !wait_for_completion_timeout(&isc->comp, 5 * HZ)) + v4l2_err(&isc->v4l2_dev, + "Timeout waiting for end of the capture\n"); + + mutex_unlock(&isc->awb_mutex); + + /* Disable DMA interrupt */ + regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE); + + pm_runtime_put_sync(isc->dev); + + /* Disable stream on the sub device */ + ret = v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD) + v4l2_err(&isc->v4l2_dev, "stream off failed in subdev\n"); + + /* Release all active buffers */ + spin_lock_irqsave(&isc->dma_queue_lock, flags); + if (unlikely(isc->cur_frm)) { + vb2_buffer_done(&isc->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + isc->cur_frm = NULL; + } + list_for_each_entry(buf, &isc->dma_queue, list) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&isc->dma_queue); + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); +} + +static void isc_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct isc_buffer *buf = container_of(vbuf, struct isc_buffer, vb); + struct isc_device *isc = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&isc->dma_queue_lock, flags); + if (!isc->cur_frm && list_empty(&isc->dma_queue) && + vb2_start_streaming_called(vb->vb2_queue)) { + isc->cur_frm = buf; + isc_start_dma(isc); + } else { + list_add_tail(&buf->list, &isc->dma_queue); + } + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); +} + +static const struct vb2_ops isc_vb2_ops = { + .queue_setup = isc_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = isc_buffer_prepare, + .start_streaming = isc_start_streaming, + .stop_streaming = isc_stop_streaming, + .buf_queue = isc_buffer_queue, + .prepare_streaming = isc_prepare_streaming, + .unprepare_streaming = isc_unprepare_streaming, +}; + +static int isc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct isc_device *isc = video_drvdata(file); + + strscpy(cap->driver, "microchip-isc", sizeof(cap->driver)); + strscpy(cap->card, "Microchip Image Sensor Controller", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", isc->v4l2_dev.name); + + return 0; +} + +static int isc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct isc_device *isc = video_drvdata(file); + u32 index = f->index; + u32 i, supported_index = 0; + struct isc_format *fmt; + + /* + * If we are not asked a specific mbus_code, we have to report all + * the formats that we can output. + */ + if (!f->mbus_code) { + if (index >= isc->controller_formats_size) + return -EINVAL; + + f->pixelformat = isc->controller_formats[index].fourcc; + + return 0; + } + + /* + * If a specific mbus_code is requested, check if we support + * this mbus_code as input for the ISC. + * If it's supported, then we report the corresponding pixelformat + * as first possible option for the ISC. + * E.g. mbus MEDIA_BUS_FMT_YUYV8_2X8 and report + * 'YUYV' (YUYV 4:2:2) + */ + fmt = isc_find_format_by_code(isc, f->mbus_code, &i); + if (!fmt) + return -EINVAL; + + if (!index) { + f->pixelformat = fmt->fourcc; + + return 0; + } + + supported_index++; + + /* If the index is not raw, we don't have anymore formats to report */ + if (!ISC_IS_FORMAT_RAW(f->mbus_code)) + return -EINVAL; + + /* + * We are asked for a specific mbus code, which is raw. + * We have to search through the formats we can convert to. + * We have to skip the raw formats, we cannot convert to raw. + * E.g. 'AR12' (16-bit ARGB 4-4-4-4), 'AR15' (16-bit ARGB 1-5-5-5), etc. + */ + for (i = 0; i < isc->controller_formats_size; i++) { + if (isc->controller_formats[i].raw) + continue; + if (index == supported_index) { + f->pixelformat = isc->controller_formats[i].fourcc; + return 0; + } + supported_index++; + } + + return -EINVAL; +} + +static int isc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct isc_device *isc = video_drvdata(file); + + *fmt = isc->fmt; + + return 0; +} + +/* + * Checks the current configured format, if ISC can output it, + * considering which type of format the ISC receives from the sensor + */ +static int isc_try_validate_formats(struct isc_device *isc) +{ + int ret; + bool bayer = false, yuv = false, rgb = false, grey = false; + + /* all formats supported by the RLP module are OK */ + switch (isc->try_config.fourcc) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SRGGB12: + ret = 0; + bayer = true; + break; + + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + ret = 0; + yuv = true; + break; + + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_XBGR32: + case V4L2_PIX_FMT_ARGB444: + case V4L2_PIX_FMT_ARGB555: + ret = 0; + rgb = true; + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y10: + case V4L2_PIX_FMT_Y16: + ret = 0; + grey = true; + break; + default: + /* any other different formats are not supported */ + v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n"); + ret = -EINVAL; + } + v4l2_dbg(1, debug, &isc->v4l2_dev, + "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n", + rgb, yuv, grey, bayer); + + if (bayer && + !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n"); + return -EINVAL; + } + + if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) && + !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) { + v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n"); + return -EINVAL; + } + + if ((rgb || bayer || yuv) && + ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) { + v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n"); + return -EINVAL; + } + + return ret; +} + +/* + * Configures the RLP and DMA modules, depending on the output format + * configured for the ISC. + * If direct_dump == true, just dump raw data 8/16 bits depending on format. + */ +static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump) +{ + isc->try_config.rlp_cfg_mode = 0; + + switch (isc->try_config.fourcc) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT8; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 8; + isc->try_config.bpp_v4l2 = 8; + break; + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT10; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SRGGB12: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT12; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_RGB565: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_RGB565; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_ARGB444: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB444; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_ARGB555: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB555; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_XBGR32: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB32; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 32; + isc->try_config.bpp_v4l2 = 32; + break; + case V4L2_PIX_FMT_YUV420: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC420P; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR; + isc->try_config.bpp = 12; + isc->try_config.bpp_v4l2 = 8; /* only first plane */ + break; + case V4L2_PIX_FMT_YUV422P: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC422P; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 8; /* only first plane */ + break; + case V4L2_PIX_FMT_YUYV: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_YUYV; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_UYVY: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_UYVY; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_VYUY: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_VYUY; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_GREY: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY8; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 8; + isc->try_config.bpp_v4l2 = 8; + break; + case V4L2_PIX_FMT_Y16: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY10 | ISC_RLP_CFG_LSH; + fallthrough; + case V4L2_PIX_FMT_Y10: + isc->try_config.rlp_cfg_mode |= ISC_RLP_CFG_MODE_DATY10; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + default: + return -EINVAL; + } + + if (direct_dump) { + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT8; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + return 0; + } + + return 0; +} + +/* + * Configuring pipeline modules, depending on which format the ISC outputs + * and considering which format it has as input from the sensor. + */ +static int isc_try_configure_pipeline(struct isc_device *isc) +{ + switch (isc->try_config.fourcc) { + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_ARGB555: + case V4L2_PIX_FMT_ARGB444: + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_XBGR32: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + WB_ENABLE | GAM_ENABLES | DPC_BLCENABLE | + CC_ENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_YUV420: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | GAM_ENABLES | WB_ENABLE | + SUB420_ENABLE | SUB422_ENABLE | CBC_ENABLE | + DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_YUV422P: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | WB_ENABLE | GAM_ENABLES | + SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | WB_ENABLE | GAM_ENABLES | + SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y16: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | WB_ENABLE | GAM_ENABLES | + CBC_ENABLE | DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + default: + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) + isc->try_config.bits_pipeline = WB_ENABLE | DPC_BLCENABLE; + else + isc->try_config.bits_pipeline = 0x0; + } + + /* Tune the pipeline to product specific */ + isc->adapt_pipeline(isc); + + return 0; +} + +static void isc_try_fse(struct isc_device *isc, + struct v4l2_subdev_state *sd_state) +{ + int ret; + struct v4l2_subdev_frame_size_enum fse = {}; + + /* + * If we do not know yet which format the subdev is using, we cannot + * do anything. + */ + if (!isc->config.sd_format) + return; + + fse.code = isc->try_config.sd_format->mbus_code; + fse.which = V4L2_SUBDEV_FORMAT_TRY; + + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, enum_frame_size, + sd_state, &fse); + /* + * Attempt to obtain format size from subdev. If not available, + * just use the maximum ISC can receive. + */ + if (ret) { + sd_state->pads->try_crop.width = isc->max_width; + sd_state->pads->try_crop.height = isc->max_height; + } else { + sd_state->pads->try_crop.width = fse.max_width; + sd_state->pads->try_crop.height = fse.max_height; + } +} + +static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f) +{ + struct v4l2_pix_format *pixfmt = &f->fmt.pix; + unsigned int i; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + isc->try_config.fourcc = isc->controller_formats[0].fourcc; + + /* find if the format requested is supported */ + for (i = 0; i < isc->controller_formats_size; i++) + if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) { + isc->try_config.fourcc = pixfmt->pixelformat; + break; + } + + isc_try_configure_rlp_dma(isc, false); + + /* Limit to Microchip ISC hardware capabilities */ + v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0, + &pixfmt->height, 16, isc->max_height, 0, 0); + /* If we did not find the requested format, we will fallback here */ + pixfmt->pixelformat = isc->try_config.fourcc; + pixfmt->colorspace = V4L2_COLORSPACE_SRGB; + pixfmt->field = V4L2_FIELD_NONE; + + pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3; + pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) * + pixfmt->height; + + isc->try_fmt = *f; + + return 0; +} + +static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f) +{ + isc_try_fmt(isc, f); + + /* make the try configuration active */ + isc->config = isc->try_config; + isc->fmt = isc->try_fmt; + + v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n", + (char *)&f->fmt.pix.pixelformat, + f->fmt.pix.width, f->fmt.pix.height); + + return 0; +} + +static int isc_validate(struct isc_device *isc) +{ + int ret; + int i; + struct isc_format *sd_fmt = NULL; + struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = isc->remote_pad, + }; + struct v4l2_subdev_pad_config pad_cfg = {}; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg, + }; + + /* Get current format from subdev */ + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL, + &format); + if (ret) + return ret; + + /* Identify the subdev's format configuration */ + for (i = 0; i < isc->formats_list_size; i++) + if (isc->formats_list[i].mbus_code == format.format.code) { + sd_fmt = &isc->formats_list[i]; + break; + } + + /* Check if the format is not supported */ + if (!sd_fmt) { + v4l2_err(&isc->v4l2_dev, + "Current subdevice is streaming a media bus code that is not supported 0x%x\n", + format.format.code); + return -EPIPE; + } + + /* At this moment we know which format the subdev will use */ + isc->try_config.sd_format = sd_fmt; + + /* If the sensor is not RAW, we can only do a direct dump */ + if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) + isc_try_configure_rlp_dma(isc, true); + + /* Limit to Microchip ISC hardware capabilities */ + v4l_bound_align_image(&format.format.width, 16, isc->max_width, 0, + &format.format.height, 16, isc->max_height, 0, 0); + + /* Check if the frame size is the same. Otherwise we may overflow */ + if (pixfmt->height != format.format.height || + pixfmt->width != format.format.width) { + v4l2_err(&isc->v4l2_dev, + "ISC not configured with the proper frame size: %dx%d\n", + format.format.width, format.format.height); + return -EPIPE; + } + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "Identified subdev using format %.4s with %dx%d %d bpp\n", + (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height, + isc->try_config.bpp); + + /* Reset and restart AWB if the subdevice changed the format */ + if (isc->try_config.sd_format && isc->config.sd_format && + isc->try_config.sd_format != isc->config.sd_format) { + isc->ctrls.hist_stat = HIST_INIT; + isc_reset_awb_ctrls(isc); + isc_update_v4l2_ctrls(isc); + } + + /* Validate formats */ + ret = isc_try_validate_formats(isc); + if (ret) + return ret; + + /* Obtain frame sizes if possible to have crop requirements ready */ + isc_try_fse(isc, &pad_state); + + /* Configure ISC pipeline for the config */ + ret = isc_try_configure_pipeline(isc); + if (ret) + return ret; + + isc->config = isc->try_config; + + v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n"); + + return 0; +} + +static int isc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isc_device *isc = video_drvdata(file); + + if (vb2_is_busy(&isc->vb2_vidq)) + return -EBUSY; + + return isc_set_fmt(isc, f); +} + +static int isc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isc_device *isc = video_drvdata(file); + + return isc_try_fmt(isc, f); +} + +static int isc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = 0; + strscpy(inp->name, "Camera", sizeof(inp->name)); + + return 0; +} + +static int isc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int isc_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int isc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct isc_device *isc = video_drvdata(file); + + return v4l2_g_parm_cap(video_devdata(file), isc->current_subdev->sd, a); +} + +static int isc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct isc_device *isc = video_drvdata(file); + + return v4l2_s_parm_cap(video_devdata(file), isc->current_subdev->sd, a); +} + +static int isc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct isc_device *isc = video_drvdata(file); + int ret = -EINVAL; + int i; + + if (fsize->index) + return -EINVAL; + + for (i = 0; i < isc->controller_formats_size; i++) + if (isc->controller_formats[i].fourcc == fsize->pixel_format) + ret = 0; + + if (ret) + return ret; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + + fsize->stepwise.min_width = 16; + fsize->stepwise.max_width = isc->max_width; + fsize->stepwise.min_height = 16; + fsize->stepwise.max_height = isc->max_height; + fsize->stepwise.step_width = 1; + fsize->stepwise.step_height = 1; + + return 0; +} + +static const struct v4l2_ioctl_ops isc_ioctl_ops = { + .vidioc_querycap = isc_querycap, + .vidioc_enum_fmt_vid_cap = isc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = isc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = isc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = isc_try_fmt_vid_cap, + + .vidioc_enum_input = isc_enum_input, + .vidioc_g_input = isc_g_input, + .vidioc_s_input = isc_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_parm = isc_g_parm, + .vidioc_s_parm = isc_s_parm, + .vidioc_enum_framesizes = isc_enum_framesizes, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int isc_open(struct file *file) +{ + struct isc_device *isc = video_drvdata(file); + struct v4l2_subdev *sd = isc->current_subdev->sd; + int ret; + + if (mutex_lock_interruptible(&isc->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + if (!v4l2_fh_is_singular_file(file)) + goto unlock; + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) { + v4l2_fh_release(file); + goto unlock; + } + + ret = isc_set_fmt(isc, &isc->fmt); + if (ret) { + v4l2_subdev_call(sd, core, s_power, 0); + v4l2_fh_release(file); + } + +unlock: + mutex_unlock(&isc->lock); + return ret; +} + +static int isc_release(struct file *file) +{ + struct isc_device *isc = video_drvdata(file); + struct v4l2_subdev *sd = isc->current_subdev->sd; + bool fh_singular; + int ret; + + mutex_lock(&isc->lock); + + fh_singular = v4l2_fh_is_singular_file(file); + + ret = _vb2_fop_release(file, NULL); + + if (fh_singular) + v4l2_subdev_call(sd, core, s_power, 0); + + mutex_unlock(&isc->lock); + + return ret; +} + +static const struct v4l2_file_operations isc_fops = { + .owner = THIS_MODULE, + .open = isc_open, + .release = isc_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +irqreturn_t microchip_isc_interrupt(int irq, void *dev_id) +{ + struct isc_device *isc = (struct isc_device *)dev_id; + struct regmap *regmap = isc->regmap; + u32 isc_intsr, isc_intmask, pending; + irqreturn_t ret = IRQ_NONE; + + regmap_read(regmap, ISC_INTSR, &isc_intsr); + regmap_read(regmap, ISC_INTMASK, &isc_intmask); + + pending = isc_intsr & isc_intmask; + + if (likely(pending & ISC_INT_DDONE)) { + spin_lock(&isc->dma_queue_lock); + if (isc->cur_frm) { + struct vb2_v4l2_buffer *vbuf = &isc->cur_frm->vb; + struct vb2_buffer *vb = &vbuf->vb2_buf; + + vb->timestamp = ktime_get_ns(); + vbuf->sequence = isc->sequence++; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + isc->cur_frm = NULL; + } + + if (!list_empty(&isc->dma_queue) && !isc->stop) { + isc->cur_frm = list_first_entry(&isc->dma_queue, + struct isc_buffer, list); + list_del(&isc->cur_frm->list); + + isc_start_dma(isc); + } + + if (isc->stop) + complete(&isc->comp); + + ret = IRQ_HANDLED; + spin_unlock(&isc->dma_queue_lock); + } + + if (pending & ISC_INT_HISDONE) { + schedule_work(&isc->awb_work); + ret = IRQ_HANDLED; + } + + return ret; +} +EXPORT_SYMBOL_GPL(microchip_isc_interrupt); + +static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) +{ + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + u32 *hist_count = &ctrls->hist_count[ctrls->hist_id]; + u32 *hist_entry = &ctrls->hist_entry[0]; + u32 i; + + *min = 0; + *max = HIST_ENTRIES; + + regmap_bulk_read(regmap, ISC_HIS_ENTRY + isc->offsets.his_entry, + hist_entry, HIST_ENTRIES); + + *hist_count = 0; + /* + * we deliberately ignore the end of the histogram, + * the most white pixels + */ + for (i = 1; i < HIST_ENTRIES; i++) { + if (*hist_entry && !*min) + *min = i; + if (*hist_entry) + *max = i; + *hist_count += i * (*hist_entry++); + } + + if (!*min) + *min = 1; + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: hist_id %u, hist_count %u", + ctrls->hist_id, *hist_count); +} + +static void isc_wb_update(struct isc_ctrls *ctrls) +{ + struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls); + u32 *hist_count = &ctrls->hist_count[0]; + u32 c, offset[4]; + u64 avg = 0; + /* We compute two gains, stretch gain and grey world gain */ + u32 s_gain[4], gw_gain[4]; + + /* + * According to Grey World, we need to set gains for R/B to normalize + * them towards the green channel. + * Thus we want to keep Green as fixed and adjust only Red/Blue + * Compute the average of the both green channels first + */ + avg = (u64)hist_count[ISC_HIS_CFG_MODE_GR] + + (u64)hist_count[ISC_HIS_CFG_MODE_GB]; + avg >>= 1; + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: green components average %llu\n", avg); + + /* Green histogram is null, nothing to do */ + if (!avg) + return; + + for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) { + /* + * the color offset is the minimum value of the histogram. + * we stretch this color to the full range by substracting + * this value from the color component. + */ + offset[c] = ctrls->hist_minmax[c][HIST_MIN_INDEX]; + /* + * The offset is always at least 1. If the offset is 1, we do + * not need to adjust it, so our result must be zero. + * the offset is computed in a histogram on 9 bits (0..512) + * but the offset in register is based on + * 12 bits pipeline (0..4096). + * we need to shift with the 3 bits that the histogram is + * ignoring + */ + ctrls->offset[c] = (offset[c] - 1) << 3; + + /* + * the offset is then taken and converted to 2's complements, + * and must be negative, as we subtract this value from the + * color components + */ + ctrls->offset[c] = -ctrls->offset[c]; + + /* + * the stretch gain is the total number of histogram bins + * divided by the actual range of color component (Max - Min) + * If we compute gain like this, the actual color component + * will be stretched to the full histogram. + * We need to shift 9 bits for precision, we have 9 bits for + * decimals + */ + s_gain[c] = (HIST_ENTRIES << 9) / + (ctrls->hist_minmax[c][HIST_MAX_INDEX] - + ctrls->hist_minmax[c][HIST_MIN_INDEX] + 1); + + /* + * Now we have to compute the gain w.r.t. the average. + * Add/lose gain to the component towards the average. + * If it happens that the component is zero, use the + * fixed point value : 1.0 gain. + */ + if (hist_count[c]) + gw_gain[c] = div_u64(avg << 9, hist_count[c]); + else + gw_gain[c] = 1 << 9; + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: component %d, s_gain %u, gw_gain %u\n", + c, s_gain[c], gw_gain[c]); + /* multiply both gains and adjust for decimals */ + ctrls->gain[c] = s_gain[c] * gw_gain[c]; + ctrls->gain[c] >>= 9; + + /* make sure we are not out of range */ + ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0)); + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: component %d, final gain %u\n", + c, ctrls->gain[c]); + } +} + +static void isc_awb_work(struct work_struct *w) +{ + struct isc_device *isc = + container_of(w, struct isc_device, awb_work); + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + u32 hist_id = ctrls->hist_id; + u32 baysel; + unsigned long flags; + u32 min, max; + int ret; + + if (ctrls->hist_stat != HIST_ENABLED) + return; + + isc_hist_count(isc, &min, &max); + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb mode %d: hist min %u , max %u\n", hist_id, min, max); + + ctrls->hist_minmax[hist_id][HIST_MIN_INDEX] = min; + ctrls->hist_minmax[hist_id][HIST_MAX_INDEX] = max; + + if (hist_id != ISC_HIS_CFG_MODE_B) { + hist_id++; + } else { + isc_wb_update(ctrls); + hist_id = ISC_HIS_CFG_MODE_GR; + } + + ctrls->hist_id = hist_id; + baysel = isc->config.sd_format->cfa_baycfg << ISC_HIS_CFG_BAYSEL_SHIFT; + + ret = pm_runtime_resume_and_get(isc->dev); + if (ret < 0) + return; + + /* + * only update if we have all the required histograms and controls + * if awb has been disabled, we need to reset registers as well. + */ + if (hist_id == ISC_HIS_CFG_MODE_GR || ctrls->awb == ISC_WB_NONE) { + /* + * It may happen that DMA Done IRQ will trigger while we are + * updating white balance registers here. + * In that case, only parts of the controls have been updated. + * We can avoid that by locking the section. + */ + spin_lock_irqsave(&isc->awb_lock, flags); + isc_update_awb_ctrls(isc); + spin_unlock_irqrestore(&isc->awb_lock, flags); + + /* + * if we are doing just the one time white balance adjustment, + * we are basically done. + */ + if (ctrls->awb == ISC_WB_ONETIME) { + v4l2_info(&isc->v4l2_dev, + "Completed one time white-balance adjustment.\n"); + /* update the v4l2 controls values */ + isc_update_v4l2_ctrls(isc); + ctrls->awb = ISC_WB_NONE; + } + } + regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his, + hist_id | baysel | ISC_HIS_CFG_RAR); + + /* + * We have to make sure the streaming has not stopped meanwhile. + * ISC requires a frame to clock the internal profile update. + * To avoid issues, lock the sequence with a mutex + */ + mutex_lock(&isc->awb_mutex); + + /* streaming is not active anymore */ + if (isc->stop) { + mutex_unlock(&isc->awb_mutex); + return; + } + + isc_update_profile(isc); + + mutex_unlock(&isc->awb_mutex); + + /* if awb has been disabled, we don't need to start another histogram */ + if (ctrls->awb) + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); + + pm_runtime_put_sync(isc->dev); +} + +static int isc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct isc_ctrls *ctrls = &isc->ctrls; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrls->brightness = ctrl->val & ISC_CBC_BRIGHT_MASK; + break; + case V4L2_CID_CONTRAST: + ctrls->contrast = ctrl->val & ISC_CBC_CONTRAST_MASK; + break; + case V4L2_CID_GAMMA: + ctrls->gamma_index = ctrl->val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops isc_ctrl_ops = { + .s_ctrl = isc_s_ctrl, +}; + +static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct isc_ctrls *ctrls = &isc->ctrls; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + if (ctrl->val == 1) + ctrls->awb = ISC_WB_AUTO; + else + ctrls->awb = ISC_WB_NONE; + + /* configure the controls with new values from v4l2 */ + if (ctrl->cluster[ISC_CTRL_R_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_R] = isc->r_gain_ctrl->val; + if (ctrl->cluster[ISC_CTRL_B_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_B] = isc->b_gain_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GR_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_GR] = isc->gr_gain_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GB_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_GB] = isc->gb_gain_ctrl->val; + + if (ctrl->cluster[ISC_CTRL_R_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_R] = isc->r_off_ctrl->val; + if (ctrl->cluster[ISC_CTRL_B_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_B] = isc->b_off_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GR_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_GR] = isc->gr_off_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GB_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_GB] = isc->gb_off_ctrl->val; + + isc_update_awb_ctrls(isc); + + mutex_lock(&isc->awb_mutex); + if (vb2_is_streaming(&isc->vb2_vidq)) { + /* + * If we are streaming, we can update profile to + * have the new settings in place. + */ + isc_update_profile(isc); + } else { + /* + * The auto cluster will activate automatically this + * control. This has to be deactivated when not + * streaming. + */ + v4l2_ctrl_activate(isc->do_wb_ctrl, false); + } + mutex_unlock(&isc->awb_mutex); + + /* if we have autowhitebalance on, start histogram procedure */ + if (ctrls->awb == ISC_WB_AUTO && + vb2_is_streaming(&isc->vb2_vidq) && + ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) + isc_set_histogram(isc, true); + + /* + * for one time whitebalance adjustment, check the button, + * if it's pressed, perform the one time operation. + */ + if (ctrls->awb == ISC_WB_NONE && + ctrl->cluster[ISC_CTRL_DO_WB]->is_new && + !(ctrl->cluster[ISC_CTRL_DO_WB]->flags & + V4L2_CTRL_FLAG_INACTIVE)) { + ctrls->awb = ISC_WB_ONETIME; + isc_set_histogram(isc, true); + v4l2_dbg(1, debug, &isc->v4l2_dev, + "One time white-balance started.\n"); + } + return 0; + } + return 0; +} + +static int isc_g_volatile_awb_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct isc_ctrls *ctrls = &isc->ctrls; + + switch (ctrl->id) { + /* being a cluster, this id will be called for every control */ + case V4L2_CID_AUTO_WHITE_BALANCE: + ctrl->cluster[ISC_CTRL_R_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_R]; + ctrl->cluster[ISC_CTRL_B_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_B]; + ctrl->cluster[ISC_CTRL_GR_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_GR]; + ctrl->cluster[ISC_CTRL_GB_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_GB]; + + ctrl->cluster[ISC_CTRL_R_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_R]; + ctrl->cluster[ISC_CTRL_B_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_B]; + ctrl->cluster[ISC_CTRL_GR_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_GR]; + ctrl->cluster[ISC_CTRL_GB_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_GB]; + break; + } + return 0; +} + +static const struct v4l2_ctrl_ops isc_awb_ops = { + .s_ctrl = isc_s_awb_ctrl, + .g_volatile_ctrl = isc_g_volatile_awb_ctrl, +}; + +#define ISC_CTRL_OFF(_name, _id, _name_str) \ + static const struct v4l2_ctrl_config _name = { \ + .ops = &isc_awb_ops, \ + .id = _id, \ + .name = _name_str, \ + .type = V4L2_CTRL_TYPE_INTEGER, \ + .flags = V4L2_CTRL_FLAG_SLIDER, \ + .min = -4095, \ + .max = 4095, \ + .step = 1, \ + .def = 0, \ + } + +ISC_CTRL_OFF(isc_r_off_ctrl, ISC_CID_R_OFFSET, "Red Component Offset"); +ISC_CTRL_OFF(isc_b_off_ctrl, ISC_CID_B_OFFSET, "Blue Component Offset"); +ISC_CTRL_OFF(isc_gr_off_ctrl, ISC_CID_GR_OFFSET, "Green Red Component Offset"); +ISC_CTRL_OFF(isc_gb_off_ctrl, ISC_CID_GB_OFFSET, "Green Blue Component Offset"); + +#define ISC_CTRL_GAIN(_name, _id, _name_str) \ + static const struct v4l2_ctrl_config _name = { \ + .ops = &isc_awb_ops, \ + .id = _id, \ + .name = _name_str, \ + .type = V4L2_CTRL_TYPE_INTEGER, \ + .flags = V4L2_CTRL_FLAG_SLIDER, \ + .min = 0, \ + .max = 8191, \ + .step = 1, \ + .def = 512, \ + } + +ISC_CTRL_GAIN(isc_r_gain_ctrl, ISC_CID_R_GAIN, "Red Component Gain"); +ISC_CTRL_GAIN(isc_b_gain_ctrl, ISC_CID_B_GAIN, "Blue Component Gain"); +ISC_CTRL_GAIN(isc_gr_gain_ctrl, ISC_CID_GR_GAIN, "Green Red Component Gain"); +ISC_CTRL_GAIN(isc_gb_gain_ctrl, ISC_CID_GB_GAIN, "Green Blue Component Gain"); + +static int isc_ctrl_init(struct isc_device *isc) +{ + const struct v4l2_ctrl_ops *ops = &isc_ctrl_ops; + struct isc_ctrls *ctrls = &isc->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret; + + ctrls->hist_stat = HIST_INIT; + isc_reset_awb_ctrls(isc); + + ret = v4l2_ctrl_handler_init(hdl, 13); + if (ret < 0) + return ret; + + /* Initialize product specific controls. For example, contrast */ + isc->config_ctrls(isc, ops); + + ctrls->brightness = 0; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -1024, 1023, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1, + isc->gamma_max); + isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + + /* do_white_balance is a button, so min,max,step,default are ignored */ + isc->do_wb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops, + V4L2_CID_DO_WHITE_BALANCE, + 0, 0, 0, 0); + + if (!isc->do_wb_ctrl) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + v4l2_ctrl_activate(isc->do_wb_ctrl, false); + + isc->r_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_r_gain_ctrl, NULL); + isc->b_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_b_gain_ctrl, NULL); + isc->gr_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gr_gain_ctrl, NULL); + isc->gb_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gb_gain_ctrl, NULL); + isc->r_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_r_off_ctrl, NULL); + isc->b_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_b_off_ctrl, NULL); + isc->gr_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gr_off_ctrl, NULL); + isc->gb_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gb_off_ctrl, NULL); + + /* + * The cluster is in auto mode with autowhitebalance enabled + * and manual mode otherwise. + */ + v4l2_ctrl_auto_cluster(10, &isc->awb_ctrl, 0, true); + + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +static int isc_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct isc_device *isc = container_of(notifier->v4l2_dev, + struct isc_device, v4l2_dev); + struct isc_subdev_entity *subdev_entity = + container_of(notifier, struct isc_subdev_entity, notifier); + int pad; + + if (video_is_registered(&isc->video_dev)) { + v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n"); + return -EBUSY; + } + + subdev_entity->sd = subdev; + + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n", + subdev->name); + return pad; + } + + isc->remote_pad = pad; + + return 0; +} + +static void isc_async_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct isc_device *isc = container_of(notifier->v4l2_dev, + struct isc_device, v4l2_dev); + mutex_destroy(&isc->awb_mutex); + cancel_work_sync(&isc->awb_work); + video_unregister_device(&isc->video_dev); + v4l2_ctrl_handler_free(&isc->ctrls.handler); +} + +struct isc_format *isc_find_format_by_code(struct isc_device *isc, + unsigned int code, int *index) +{ + struct isc_format *fmt = &isc->formats_list[0]; + unsigned int i; + + for (i = 0; i < isc->formats_list_size; i++) { + if (fmt->mbus_code == code) { + *index = i; + return fmt; + } + + fmt++; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(isc_find_format_by_code); + +static int isc_set_default_fmt(struct isc_device *isc) +{ + struct v4l2_format f = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .field = V4L2_FIELD_NONE, + .pixelformat = isc->controller_formats[0].fourcc, + }, + }; + int ret; + + ret = isc_try_fmt(isc, &f); + if (ret) + return ret; + + isc->fmt = f; + return 0; +} + +static int isc_async_complete(struct v4l2_async_notifier *notifier) +{ + struct isc_device *isc = container_of(notifier->v4l2_dev, + struct isc_device, v4l2_dev); + struct video_device *vdev = &isc->video_dev; + struct vb2_queue *q = &isc->vb2_vidq; + int ret = 0; + + INIT_WORK(&isc->awb_work, isc_awb_work); + + ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, "Failed to register subdev nodes\n"); + return ret; + } + + isc->current_subdev = container_of(notifier, + struct isc_subdev_entity, notifier); + mutex_init(&isc->lock); + mutex_init(&isc->awb_mutex); + + init_completion(&isc->comp); + + /* Initialize videobuf2 queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + q->drv_priv = isc; + q->buf_struct_size = sizeof(struct isc_buffer); + q->ops = &isc_vb2_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &isc->lock; + q->min_buffers_needed = 1; + q->dev = isc->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, + "vb2_queue_init() failed: %d\n", ret); + goto isc_async_complete_err; + } + + /* Init video dma queues */ + INIT_LIST_HEAD(&isc->dma_queue); + spin_lock_init(&isc->dma_queue_lock); + spin_lock_init(&isc->awb_lock); + + ret = isc_set_default_fmt(isc); + if (ret) { + v4l2_err(&isc->v4l2_dev, "Could not set default format\n"); + goto isc_async_complete_err; + } + + ret = isc_ctrl_init(isc); + if (ret) { + v4l2_err(&isc->v4l2_dev, "Init isc ctrols failed: %d\n", ret); + goto isc_async_complete_err; + } + + /* Register video device */ + strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); + vdev->release = video_device_release_empty; + vdev->fops = &isc_fops; + vdev->ioctl_ops = &isc_ioctl_ops; + vdev->v4l2_dev = &isc->v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = q; + vdev->lock = &isc->lock; + vdev->ctrl_handler = &isc->ctrls.handler; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_IO_MC; + video_set_drvdata(vdev, isc); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, + "video_register_device failed: %d\n", ret); + goto isc_async_complete_err; + } + + ret = isc_scaler_link(isc); + if (ret < 0) + goto isc_async_complete_unregister_device; + + ret = media_device_register(&isc->mdev); + if (ret < 0) + goto isc_async_complete_unregister_device; + + return 0; + +isc_async_complete_unregister_device: + video_unregister_device(vdev); + +isc_async_complete_err: + mutex_destroy(&isc->awb_mutex); + mutex_destroy(&isc->lock); + return ret; +} + +const struct v4l2_async_notifier_operations microchip_isc_async_ops = { + .bound = isc_async_bound, + .unbind = isc_async_unbind, + .complete = isc_async_complete, +}; +EXPORT_SYMBOL_GPL(microchip_isc_async_ops); + +void microchip_isc_subdev_cleanup(struct isc_device *isc) +{ + struct isc_subdev_entity *subdev_entity; + + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { + v4l2_async_nf_unregister(&subdev_entity->notifier); + v4l2_async_nf_cleanup(&subdev_entity->notifier); + } + + INIT_LIST_HEAD(&isc->subdev_entities); +} +EXPORT_SYMBOL_GPL(microchip_isc_subdev_cleanup); + +int microchip_isc_pipeline_init(struct isc_device *isc) +{ + struct device *dev = isc->dev; + struct regmap *regmap = isc->regmap; + struct regmap_field *regs; + unsigned int i; + + /* + * DPCEN-->GDCEN-->BLCEN-->WB-->CFA-->CC--> + * GAM-->VHXS-->CSC-->CBC-->SUB422-->SUB420 + */ + const struct reg_field regfields[ISC_PIPE_LINE_NODE_NUM] = { + REG_FIELD(ISC_DPC_CTRL, 0, 0), + REG_FIELD(ISC_DPC_CTRL, 1, 1), + REG_FIELD(ISC_DPC_CTRL, 2, 2), + REG_FIELD(ISC_WB_CTRL, 0, 0), + REG_FIELD(ISC_CFA_CTRL, 0, 0), + REG_FIELD(ISC_CC_CTRL, 0, 0), + REG_FIELD(ISC_GAM_CTRL, 0, 0), + REG_FIELD(ISC_GAM_CTRL, 1, 1), + REG_FIELD(ISC_GAM_CTRL, 2, 2), + REG_FIELD(ISC_GAM_CTRL, 3, 3), + REG_FIELD(ISC_VHXS_CTRL, 0, 0), + REG_FIELD(ISC_CSC_CTRL + isc->offsets.csc, 0, 0), + REG_FIELD(ISC_CBC_CTRL + isc->offsets.cbc, 0, 0), + REG_FIELD(ISC_SUB422_CTRL + isc->offsets.sub422, 0, 0), + REG_FIELD(ISC_SUB420_CTRL + isc->offsets.sub420, 0, 0), + }; + + for (i = 0; i < ISC_PIPE_LINE_NODE_NUM; i++) { + regs = devm_regmap_field_alloc(dev, regmap, regfields[i]); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + isc->pipeline[i] = regs; + } + + return 0; +} +EXPORT_SYMBOL_GPL(microchip_isc_pipeline_init); + +static int isc_link_validate(struct media_link *link) +{ + struct video_device *vdev = + media_entity_to_video_device(link->sink->entity); + struct isc_device *isc = video_get_drvdata(vdev); + int ret; + + ret = v4l2_subdev_link_validate(link); + if (ret) + return ret; + + return isc_validate(isc); +} + +static const struct media_entity_operations isc_entity_operations = { + .link_validate = isc_link_validate, +}; + +int isc_mc_init(struct isc_device *isc, u32 ver) +{ + const struct of_device_id *match; + int ret; + + isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L; + isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT; + isc->video_dev.entity.ops = &isc_entity_operations; + + isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + + ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM, + isc->pads); + if (ret < 0) { + dev_err(isc->dev, "media entity init failed\n"); + return ret; + } + + isc->mdev.dev = isc->dev; + + match = of_match_node(isc->dev->driver->of_match_table, + isc->dev->of_node); + + strscpy(isc->mdev.driver_name, KBUILD_MODNAME, + sizeof(isc->mdev.driver_name)); + strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model)); + snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s", + isc->v4l2_dev.name); + isc->mdev.hw_revision = ver; + + media_device_init(&isc->mdev); + + isc->v4l2_dev.mdev = &isc->mdev; + + return isc_scaler_init(isc); +} +EXPORT_SYMBOL_GPL(isc_mc_init); + +void isc_mc_cleanup(struct isc_device *isc) +{ + media_entity_cleanup(&isc->video_dev.entity); + media_device_cleanup(&isc->mdev); +} +EXPORT_SYMBOL_GPL(isc_mc_cleanup); + +/* regmap configuration */ +#define MICROCHIP_ISC_REG_MAX 0xd5c +const struct regmap_config microchip_isc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = MICROCHIP_ISC_REG_MAX, +}; +EXPORT_SYMBOL_GPL(microchip_isc_regmap_config); + +MODULE_AUTHOR("Songjun Wu"); +MODULE_AUTHOR("Eugen Hristev"); +MODULE_DESCRIPTION("Microchip ISC common code base"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/microchip/microchip-isc-clk.c b/drivers/media/platform/microchip/microchip-isc-clk.c new file mode 100644 index 000000000000..24358d804e75 --- /dev/null +++ b/drivers/media/platform/microchip/microchip-isc-clk.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip Image Sensor Controller (ISC) common clock driver setup + * + * Copyright (C) 2016 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev <eugen.hristev@microchip.com> + * + */ +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include "microchip-isc-regs.h" +#include "microchip-isc.h" + +static int isc_wait_clk_stable(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + struct regmap *regmap = isc_clk->regmap; + unsigned long timeout = jiffies + usecs_to_jiffies(1000); + unsigned int status; + + while (time_before(jiffies, timeout)) { + regmap_read(regmap, ISC_CLKSR, &status); + if (!(status & ISC_CLKSR_SIP)) + return 0; + + usleep_range(10, 250); + } + + return -ETIMEDOUT; +} + +static int isc_clk_prepare(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + int ret; + + ret = pm_runtime_resume_and_get(isc_clk->dev); + if (ret < 0) + return ret; + + return isc_wait_clk_stable(hw); +} + +static void isc_clk_unprepare(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + isc_wait_clk_stable(hw); + + pm_runtime_put_sync(isc_clk->dev); +} + +static int isc_clk_enable(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 id = isc_clk->id; + struct regmap *regmap = isc_clk->regmap; + unsigned long flags; + unsigned int status; + + dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n", + __func__, id, isc_clk->div, isc_clk->parent_id); + + spin_lock_irqsave(&isc_clk->lock, flags); + regmap_update_bits(regmap, ISC_CLKCFG, + ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id), + (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) | + (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id))); + + regmap_write(regmap, ISC_CLKEN, ISC_CLK(id)); + spin_unlock_irqrestore(&isc_clk->lock, flags); + + regmap_read(regmap, ISC_CLKSR, &status); + if (status & ISC_CLK(id)) + return 0; + else + return -EINVAL; +} + +static void isc_clk_disable(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 id = isc_clk->id; + unsigned long flags; + + spin_lock_irqsave(&isc_clk->lock, flags); + regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id)); + spin_unlock_irqrestore(&isc_clk->lock, flags); +} + +static int isc_clk_is_enabled(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 status; + int ret; + + ret = pm_runtime_resume_and_get(isc_clk->dev); + if (ret < 0) + return 0; + + regmap_read(isc_clk->regmap, ISC_CLKSR, &status); + + pm_runtime_put_sync(isc_clk->dev); + + return status & ISC_CLK(isc_clk->id) ? 1 : 0; +} + +static unsigned long +isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1); +} + +static int isc_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + long best_rate = -EINVAL; + int best_diff = -1; + unsigned int i, div; + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + struct clk_hw *parent; + unsigned long parent_rate; + + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + continue; + + for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) { + unsigned long rate; + int diff; + + rate = DIV_ROUND_CLOSEST(parent_rate, div); + diff = abs(req->rate - rate); + + if (best_diff < 0 || best_diff > diff) { + best_rate = rate; + best_diff = diff; + req->best_parent_rate = parent_rate; + req->best_parent_hw = parent; + } + + if (!best_diff || rate < req->rate) + break; + } + + if (!best_diff) + break; + } + + dev_dbg(isc_clk->dev, + "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n", + __func__, best_rate, + __clk_get_name((req->best_parent_hw)->clk), + req->best_parent_rate); + + if (best_rate < 0) + return best_rate; + + req->rate = best_rate; + + return 0; +} + +static int isc_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + if (index >= clk_hw_get_num_parents(hw)) + return -EINVAL; + + isc_clk->parent_id = index; + + return 0; +} + +static u8 isc_clk_get_parent(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + return isc_clk->parent_id; +} + +static int isc_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 div; + + if (!rate) + return -EINVAL; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + if (div > (ISC_CLK_MAX_DIV + 1) || !div) + return -EINVAL; + + isc_clk->div = div - 1; + + return 0; +} + +static const struct clk_ops isc_clk_ops = { + .prepare = isc_clk_prepare, + .unprepare = isc_clk_unprepare, + .enable = isc_clk_enable, + .disable = isc_clk_disable, + .is_enabled = isc_clk_is_enabled, + .recalc_rate = isc_clk_recalc_rate, + .determine_rate = isc_clk_determine_rate, + .set_parent = isc_clk_set_parent, + .get_parent = isc_clk_get_parent, + .set_rate = isc_clk_set_rate, +}; + +static int isc_clk_register(struct isc_device *isc, unsigned int id) +{ + struct regmap *regmap = isc->regmap; + struct device_node *np = isc->dev->of_node; + struct isc_clk *isc_clk; + struct clk_init_data init; + const char *clk_name = np->name; + const char *parent_names[3]; + int num_parents; + + if (id == ISC_ISPCK && !isc->ispck_required) + return 0; + + num_parents = of_clk_get_parent_count(np); + if (num_parents < 1 || num_parents > 3) + return -EINVAL; + + if (num_parents > 2 && id == ISC_ISPCK) + num_parents = 2; + + of_clk_parent_fill(np, parent_names, num_parents); + + if (id == ISC_MCK) + of_property_read_string(np, "clock-output-names", &clk_name); + else + clk_name = "isc-ispck"; + + init.parent_names = parent_names; + init.num_parents = num_parents; + init.name = clk_name; + init.ops = &isc_clk_ops; + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; + + isc_clk = &isc->isc_clks[id]; + isc_clk->hw.init = &init; + isc_clk->regmap = regmap; + isc_clk->id = id; + isc_clk->dev = isc->dev; + spin_lock_init(&isc_clk->lock); + + isc_clk->clk = clk_register(isc->dev, &isc_clk->hw); + if (IS_ERR(isc_clk->clk)) { + dev_err(isc->dev, "%s: clock register fail\n", clk_name); + return PTR_ERR(isc_clk->clk); + } else if (id == ISC_MCK) { + of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk); + } + + return 0; +} + +int microchip_isc_clk_init(struct isc_device *isc) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) + isc->isc_clks[i].clk = ERR_PTR(-EINVAL); + + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) { + ret = isc_clk_register(isc, i); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(microchip_isc_clk_init); + +void microchip_isc_clk_cleanup(struct isc_device *isc) +{ + unsigned int i; + + of_clk_del_provider(isc->dev->of_node); + + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) { + struct isc_clk *isc_clk = &isc->isc_clks[i]; + + if (!IS_ERR(isc_clk->clk)) + clk_unregister(isc_clk->clk); + } +} +EXPORT_SYMBOL_GPL(microchip_isc_clk_cleanup); diff --git a/drivers/media/platform/microchip/microchip-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h new file mode 100644 index 000000000000..e77e1d9a1db8 --- /dev/null +++ b/drivers/media/platform/microchip/microchip-isc-regs.h @@ -0,0 +1,413 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MICROCHIP_ISC_REGS_H +#define __MICROCHIP_ISC_REGS_H + +#include <linux/bitops.h> + +/* ISC Control Enable Register 0 */ +#define ISC_CTRLEN 0x00000000 + +/* ISC Control Disable Register 0 */ +#define ISC_CTRLDIS 0x00000004 + +/* ISC Control Status Register 0 */ +#define ISC_CTRLSR 0x00000008 + +#define ISC_CTRL_CAPTURE BIT(0) +#define ISC_CTRL_UPPRO BIT(1) +#define ISC_CTRL_HISREQ BIT(2) +#define ISC_CTRL_HISCLR BIT(3) + +/* ISC Parallel Front End Configuration 0 Register */ +#define ISC_PFE_CFG0 0x0000000c + +#define ISC_PFE_CFG0_HPOL_LOW BIT(0) +#define ISC_PFE_CFG0_VPOL_LOW BIT(1) +#define ISC_PFE_CFG0_PPOL_LOW BIT(2) +#define ISC_PFE_CFG0_CCIR656 BIT(9) +#define ISC_PFE_CFG0_CCIR_CRC BIT(10) +#define ISC_PFE_CFG0_MIPI BIT(14) + +#define ISC_PFE_CFG0_MODE_PROGRESSIVE (0x0 << 4) +#define ISC_PFE_CFG0_MODE_MASK GENMASK(6, 4) + +#define ISC_PFE_CFG0_BPS_EIGHT (0x4 << 28) +#define ISC_PFG_CFG0_BPS_NINE (0x3 << 28) +#define ISC_PFG_CFG0_BPS_TEN (0x2 << 28) +#define ISC_PFG_CFG0_BPS_ELEVEN (0x1 << 28) +#define ISC_PFG_CFG0_BPS_TWELVE (0x0 << 28) +#define ISC_PFE_CFG0_BPS_MASK GENMASK(30, 28) + +#define ISC_PFE_CFG0_COLEN BIT(12) +#define ISC_PFE_CFG0_ROWEN BIT(13) + +/* ISC Parallel Front End Configuration 1 Register */ +#define ISC_PFE_CFG1 0x00000010 + +#define ISC_PFE_CFG1_COLMIN(v) ((v)) +#define ISC_PFE_CFG1_COLMIN_MASK GENMASK(15, 0) +#define ISC_PFE_CFG1_COLMAX(v) ((v) << 16) +#define ISC_PFE_CFG1_COLMAX_MASK GENMASK(31, 16) + +/* ISC Parallel Front End Configuration 2 Register */ +#define ISC_PFE_CFG2 0x00000014 + +#define ISC_PFE_CFG2_ROWMIN(v) ((v)) +#define ISC_PFE_CFG2_ROWMIN_MASK GENMASK(15, 0) +#define ISC_PFE_CFG2_ROWMAX(v) ((v) << 16) +#define ISC_PFE_CFG2_ROWMAX_MASK GENMASK(31, 16) + +/* ISC Clock Enable Register */ +#define ISC_CLKEN 0x00000018 + +/* ISC Clock Disable Register */ +#define ISC_CLKDIS 0x0000001c + +/* ISC Clock Status Register */ +#define ISC_CLKSR 0x00000020 +#define ISC_CLKSR_SIP BIT(31) + +#define ISC_CLK(n) BIT(n) + +/* ISC Clock Configuration Register */ +#define ISC_CLKCFG 0x00000024 +#define ISC_CLKCFG_DIV_SHIFT(n) ((n) * 16) +#define ISC_CLKCFG_DIV_MASK(n) GENMASK(((n) * 16 + 7), (n) * 16) +#define ISC_CLKCFG_SEL_SHIFT(n) ((n) * 16 + 8) +#define ISC_CLKCFG_SEL_MASK(n) GENMASK(((n) * 17 + 8), ((n) * 16 + 8)) + +/* ISC Interrupt Enable Register */ +#define ISC_INTEN 0x00000028 + +/* ISC Interrupt Disable Register */ +#define ISC_INTDIS 0x0000002c + +/* ISC Interrupt Mask Register */ +#define ISC_INTMASK 0x00000030 + +/* ISC Interrupt Status Register */ +#define ISC_INTSR 0x00000034 + +#define ISC_INT_DDONE BIT(8) +#define ISC_INT_HISDONE BIT(12) + +/* ISC DPC Control Register */ +#define ISC_DPC_CTRL 0x40 + +#define ISC_DPC_CTRL_DPCEN BIT(0) +#define ISC_DPC_CTRL_GDCEN BIT(1) +#define ISC_DPC_CTRL_BLCEN BIT(2) + +/* ISC DPC Config Register */ +#define ISC_DPC_CFG 0x44 + +#define ISC_DPC_CFG_BAYSEL_SHIFT 0 + +#define ISC_DPC_CFG_EITPOL BIT(4) + +#define ISC_DPC_CFG_TA_ENABLE BIT(14) +#define ISC_DPC_CFG_TC_ENABLE BIT(13) +#define ISC_DPC_CFG_TM_ENABLE BIT(12) + +#define ISC_DPC_CFG_RE_MODE BIT(17) + +#define ISC_DPC_CFG_GDCCLP_SHIFT 20 +#define ISC_DPC_CFG_GDCCLP_MASK GENMASK(22, 20) + +#define ISC_DPC_CFG_BLOFF_SHIFT 24 +#define ISC_DPC_CFG_BLOFF_MASK GENMASK(31, 24) + +#define ISC_DPC_CFG_BAYCFG_SHIFT 0 +#define ISC_DPC_CFG_BAYCFG_MASK GENMASK(1, 0) +/* ISC DPC Threshold Median Register */ +#define ISC_DPC_THRESHM 0x48 + +/* ISC DPC Threshold Closest Register */ +#define ISC_DPC_THRESHC 0x4C + +/* ISC DPC Threshold Average Register */ +#define ISC_DPC_THRESHA 0x50 + +/* ISC DPC STatus Register */ +#define ISC_DPC_SR 0x54 + +/* ISC White Balance Control Register */ +#define ISC_WB_CTRL 0x00000058 + +/* ISC White Balance Configuration Register */ +#define ISC_WB_CFG 0x0000005c + +/* ISC White Balance Offset for R, GR Register */ +#define ISC_WB_O_RGR 0x00000060 + +/* ISC White Balance Offset for B, GB Register */ +#define ISC_WB_O_BGB 0x00000064 + +/* ISC White Balance Gain for R, GR Register */ +#define ISC_WB_G_RGR 0x00000068 + +/* ISC White Balance Gain for B, GB Register */ +#define ISC_WB_G_BGB 0x0000006c + +/* ISC Color Filter Array Control Register */ +#define ISC_CFA_CTRL 0x00000070 + +/* ISC Color Filter Array Configuration Register */ +#define ISC_CFA_CFG 0x00000074 +#define ISC_CFA_CFG_EITPOL BIT(4) + +#define ISC_BAY_CFG_GRGR 0x0 +#define ISC_BAY_CFG_RGRG 0x1 +#define ISC_BAY_CFG_GBGB 0x2 +#define ISC_BAY_CFG_BGBG 0x3 + +/* ISC Color Correction Control Register */ +#define ISC_CC_CTRL 0x00000078 + +/* ISC Color Correction RR RG Register */ +#define ISC_CC_RR_RG 0x0000007c + +/* ISC Color Correction RB OR Register */ +#define ISC_CC_RB_OR 0x00000080 + +/* ISC Color Correction GR GG Register */ +#define ISC_CC_GR_GG 0x00000084 + +/* ISC Color Correction GB OG Register */ +#define ISC_CC_GB_OG 0x00000088 + +/* ISC Color Correction BR BG Register */ +#define ISC_CC_BR_BG 0x0000008c + +/* ISC Color Correction BB OB Register */ +#define ISC_CC_BB_OB 0x00000090 + +/* ISC Gamma Correction Control Register */ +#define ISC_GAM_CTRL 0x00000094 + +#define ISC_GAM_CTRL_BIPART BIT(4) + +/* ISC_Gamma Correction Blue Entry Register */ +#define ISC_GAM_BENTRY 0x00000098 + +/* ISC_Gamma Correction Green Entry Register */ +#define ISC_GAM_GENTRY 0x00000198 + +/* ISC_Gamma Correction Green Entry Register */ +#define ISC_GAM_RENTRY 0x00000298 + +/* ISC VHXS Control Register */ +#define ISC_VHXS_CTRL 0x398 + +/* ISC VHXS Source Size Register */ +#define ISC_VHXS_SS 0x39C + +/* ISC VHXS Destination Size Register */ +#define ISC_VHXS_DS 0x3A0 + +/* ISC Vertical Factor Register */ +#define ISC_VXS_FACT 0x3a4 + +/* ISC Horizontal Factor Register */ +#define ISC_HXS_FACT 0x3a8 + +/* ISC Vertical Config Register */ +#define ISC_VXS_CFG 0x3ac + +/* ISC Horizontal Config Register */ +#define ISC_HXS_CFG 0x3b0 + +/* ISC Vertical Tap Register */ +#define ISC_VXS_TAP 0x3b4 + +/* ISC Horizontal Tap Register */ +#define ISC_HXS_TAP 0x434 + +/* Offset for CSC register specific to sama5d2 product */ +#define ISC_SAMA5D2_CSC_OFFSET 0 +/* Offset for CSC register specific to sama7g5 product */ +#define ISC_SAMA7G5_CSC_OFFSET 0x11c + +/* Color Space Conversion Control Register */ +#define ISC_CSC_CTRL 0x00000398 + +/* Color Space Conversion YR YG Register */ +#define ISC_CSC_YR_YG 0x0000039c + +/* Color Space Conversion YB OY Register */ +#define ISC_CSC_YB_OY 0x000003a0 + +/* Color Space Conversion CBR CBG Register */ +#define ISC_CSC_CBR_CBG 0x000003a4 + +/* Color Space Conversion CBB OCB Register */ +#define ISC_CSC_CBB_OCB 0x000003a8 + +/* Color Space Conversion CRR CRG Register */ +#define ISC_CSC_CRR_CRG 0x000003ac + +/* Color Space Conversion CRB OCR Register */ +#define ISC_CSC_CRB_OCR 0x000003b0 + +/* Offset for CBC register specific to sama5d2 product */ +#define ISC_SAMA5D2_CBC_OFFSET 0 +/* Offset for CBC register specific to sama7g5 product */ +#define ISC_SAMA7G5_CBC_OFFSET 0x11c + +/* Contrast And Brightness Control Register */ +#define ISC_CBC_CTRL 0x000003b4 + +/* Contrast And Brightness Configuration Register */ +#define ISC_CBC_CFG 0x000003b8 + +/* Brightness Register */ +#define ISC_CBC_BRIGHT 0x000003bc +#define ISC_CBC_BRIGHT_MASK GENMASK(10, 0) + +/* Contrast Register */ +#define ISC_CBC_CONTRAST 0x000003c0 +#define ISC_CBC_CONTRAST_MASK GENMASK(11, 0) + +/* Hue Register */ +#define ISC_CBCHS_HUE 0x4e0 +/* Saturation Register */ +#define ISC_CBCHS_SAT 0x4e4 + +/* Offset for SUB422 register specific to sama5d2 product */ +#define ISC_SAMA5D2_SUB422_OFFSET 0 +/* Offset for SUB422 register specific to sama7g5 product */ +#define ISC_SAMA7G5_SUB422_OFFSET 0x124 + +/* Subsampling 4:4:4 to 4:2:2 Control Register */ +#define ISC_SUB422_CTRL 0x000003c4 + +/* Offset for SUB420 register specific to sama5d2 product */ +#define ISC_SAMA5D2_SUB420_OFFSET 0 +/* Offset for SUB420 register specific to sama7g5 product */ +#define ISC_SAMA7G5_SUB420_OFFSET 0x124 +/* Subsampling 4:2:2 to 4:2:0 Control Register */ +#define ISC_SUB420_CTRL 0x000003cc + +/* Offset for RLP register specific to sama5d2 product */ +#define ISC_SAMA5D2_RLP_OFFSET 0 +/* Offset for RLP register specific to sama7g5 product */ +#define ISC_SAMA7G5_RLP_OFFSET 0x124 +/* Rounding, Limiting and Packing Configuration Register */ +#define ISC_RLP_CFG 0x000003d0 + +#define ISC_RLP_CFG_MODE_DAT8 0x0 +#define ISC_RLP_CFG_MODE_DAT9 0x1 +#define ISC_RLP_CFG_MODE_DAT10 0x2 +#define ISC_RLP_CFG_MODE_DAT11 0x3 +#define ISC_RLP_CFG_MODE_DAT12 0x4 +#define ISC_RLP_CFG_MODE_DATY8 0x5 +#define ISC_RLP_CFG_MODE_DATY10 0x6 +#define ISC_RLP_CFG_MODE_ARGB444 0x7 +#define ISC_RLP_CFG_MODE_ARGB555 0x8 +#define ISC_RLP_CFG_MODE_RGB565 0x9 +#define ISC_RLP_CFG_MODE_ARGB32 0xa +#define ISC_RLP_CFG_MODE_YYCC 0xb +#define ISC_RLP_CFG_MODE_YYCC_LIMITED 0xc +#define ISC_RLP_CFG_MODE_YCYC 0xd +#define ISC_RLP_CFG_MODE_MASK GENMASK(3, 0) + +#define ISC_RLP_CFG_LSH BIT(5) + +#define ISC_RLP_CFG_YMODE_YUYV (3 << 6) +#define ISC_RLP_CFG_YMODE_YVYU (2 << 6) +#define ISC_RLP_CFG_YMODE_VYUY (0 << 6) +#define ISC_RLP_CFG_YMODE_UYVY (1 << 6) + +#define ISC_RLP_CFG_YMODE_MASK GENMASK(7, 6) + +/* Offset for HIS register specific to sama5d2 product */ +#define ISC_SAMA5D2_HIS_OFFSET 0 +/* Offset for HIS register specific to sama7g5 product */ +#define ISC_SAMA7G5_HIS_OFFSET 0x124 +/* Histogram Control Register */ +#define ISC_HIS_CTRL 0x000003d4 + +#define ISC_HIS_CTRL_EN BIT(0) +#define ISC_HIS_CTRL_DIS 0x0 + +/* Histogram Configuration Register */ +#define ISC_HIS_CFG 0x000003d8 + +#define ISC_HIS_CFG_MODE_GR 0x0 +#define ISC_HIS_CFG_MODE_R 0x1 +#define ISC_HIS_CFG_MODE_GB 0x2 +#define ISC_HIS_CFG_MODE_B 0x3 +#define ISC_HIS_CFG_MODE_Y 0x4 +#define ISC_HIS_CFG_MODE_RAW 0x5 +#define ISC_HIS_CFG_MODE_YCCIR656 0x6 + +#define ISC_HIS_CFG_BAYSEL_SHIFT 4 + +#define ISC_HIS_CFG_RAR BIT(8) + +/* Offset for DMA register specific to sama5d2 product */ +#define ISC_SAMA5D2_DMA_OFFSET 0 +/* Offset for DMA register specific to sama7g5 product */ +#define ISC_SAMA7G5_DMA_OFFSET 0x13c + +/* DMA Configuration Register */ +#define ISC_DCFG 0x000003e0 +#define ISC_DCFG_IMODE_PACKED8 0x0 +#define ISC_DCFG_IMODE_PACKED16 0x1 +#define ISC_DCFG_IMODE_PACKED32 0x2 +#define ISC_DCFG_IMODE_YC422SP 0x3 +#define ISC_DCFG_IMODE_YC422P 0x4 +#define ISC_DCFG_IMODE_YC420SP 0x5 +#define ISC_DCFG_IMODE_YC420P 0x6 +#define ISC_DCFG_IMODE_MASK GENMASK(2, 0) + +#define ISC_DCFG_YMBSIZE_SINGLE (0x0 << 4) +#define ISC_DCFG_YMBSIZE_BEATS4 (0x1 << 4) +#define ISC_DCFG_YMBSIZE_BEATS8 (0x2 << 4) +#define ISC_DCFG_YMBSIZE_BEATS16 (0x3 << 4) +#define ISC_DCFG_YMBSIZE_BEATS32 (0x4 << 4) +#define ISC_DCFG_YMBSIZE_MASK GENMASK(6, 4) + +#define ISC_DCFG_CMBSIZE_SINGLE (0x0 << 8) +#define ISC_DCFG_CMBSIZE_BEATS4 (0x1 << 8) +#define ISC_DCFG_CMBSIZE_BEATS8 (0x2 << 8) +#define ISC_DCFG_CMBSIZE_BEATS16 (0x3 << 8) +#define ISC_DCFG_CMBSIZE_BEATS32 (0x4 << 8) +#define ISC_DCFG_CMBSIZE_MASK GENMASK(10, 8) + +/* DMA Control Register */ +#define ISC_DCTRL 0x000003e4 + +#define ISC_DCTRL_DVIEW_PACKED (0x0 << 1) +#define ISC_DCTRL_DVIEW_SEMIPLANAR (0x1 << 1) +#define ISC_DCTRL_DVIEW_PLANAR (0x2 << 1) +#define ISC_DCTRL_DVIEW_MASK GENMASK(2, 1) + +#define ISC_DCTRL_IE_IS (0x0 << 4) + +/* DMA Descriptor Address Register */ +#define ISC_DNDA 0x000003e8 + +/* DMA Address 0 Register */ +#define ISC_DAD0 0x000003ec + +/* DMA Address 1 Register */ +#define ISC_DAD1 0x000003f4 + +/* DMA Address 2 Register */ +#define ISC_DAD2 0x000003fc + +/* Offset for version register specific to sama5d2 product */ +#define ISC_SAMA5D2_VERSION_OFFSET 0 +#define ISC_SAMA7G5_VERSION_OFFSET 0x13c +/* Version Register */ +#define ISC_VERSION 0x0000040c + +/* Offset for version register specific to sama5d2 product */ +#define ISC_SAMA5D2_HIS_ENTRY_OFFSET 0 +/* Offset for version register specific to sama7g5 product */ +#define ISC_SAMA7G5_HIS_ENTRY_OFFSET 0x14c +/* Histogram Entry */ +#define ISC_HIS_ENTRY 0x00000410 + +#endif diff --git a/drivers/media/platform/microchip/microchip-isc-scaler.c b/drivers/media/platform/microchip/microchip-isc-scaler.c new file mode 100644 index 000000000000..0f29a32d15ce --- /dev/null +++ b/drivers/media/platform/microchip/microchip-isc-scaler.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip Image Sensor Controller (ISC) Scaler entity support + * + * Copyright (C) 2022 Microchip Technology, Inc. + * + * Author: Eugen Hristev <eugen.hristev@microchip.com> + * + */ + +#include <media/media-device.h> +#include <media/media-entity.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "microchip-isc-regs.h" +#include "microchip-isc.h" + +static void isc_scaler_prepare_fmt(struct v4l2_mbus_framefmt *framefmt) +{ + framefmt->colorspace = V4L2_COLORSPACE_SRGB; + framefmt->field = V4L2_FIELD_NONE; + framefmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + framefmt->quantization = V4L2_QUANTIZATION_DEFAULT; + framefmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +}; + +static int isc_scaler_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + struct v4l2_mbus_framefmt *v4l2_try_fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state, + format->pad); + format->format = *v4l2_try_fmt; + + return 0; + } + + format->format = isc->scaler_format[format->pad]; + + return 0; +} + +static int isc_scaler_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *req_fmt) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + struct v4l2_mbus_framefmt *v4l2_try_fmt; + struct isc_format *fmt; + unsigned int i; + + /* Source format is fixed, we cannot change it */ + if (req_fmt->pad == ISC_SCALER_PAD_SOURCE) { + req_fmt->format = isc->scaler_format[ISC_SCALER_PAD_SOURCE]; + return 0; + } + + /* There is no limit on the frame size on the sink pad */ + v4l_bound_align_image(&req_fmt->format.width, 16, UINT_MAX, 0, + &req_fmt->format.height, 16, UINT_MAX, 0, 0); + + isc_scaler_prepare_fmt(&req_fmt->format); + + fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i); + + if (!fmt) + fmt = &isc->formats_list[0]; + + req_fmt->format.code = fmt->mbus_code; + + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state, + req_fmt->pad); + *v4l2_try_fmt = req_fmt->format; + /* Trying on the sink pad makes the source pad change too */ + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state, + ISC_SCALER_PAD_SOURCE); + *v4l2_try_fmt = req_fmt->format; + + v4l_bound_align_image(&v4l2_try_fmt->width, + 16, isc->max_width, 0, + &v4l2_try_fmt->height, + 16, isc->max_height, 0, 0); + /* if we are just trying, we are done */ + return 0; + } + + isc->scaler_format[ISC_SCALER_PAD_SINK] = req_fmt->format; + + /* The source pad is the same as the sink, but we have to crop it */ + isc->scaler_format[ISC_SCALER_PAD_SOURCE] = + isc->scaler_format[ISC_SCALER_PAD_SINK]; + v4l_bound_align_image + (&isc->scaler_format[ISC_SCALER_PAD_SOURCE].width, 16, + isc->max_width, 0, + &isc->scaler_format[ISC_SCALER_PAD_SOURCE].height, 16, + isc->max_height, 0, 0); + + return 0; +} + +static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + + /* + * All formats supported by the ISC are supported by the scaler. + * Advertise the formats which the ISC can take as input, as the scaler + * entity cropping is part of the PFE module (parallel front end) + */ + if (code->index < isc->formats_list_size) { + code->code = isc->formats_list[code->index].mbus_code; + return 0; + } + + return -EINVAL; +} + +static int isc_scaler_g_sel(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + + if (sel->pad == ISC_SCALER_PAD_SOURCE) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS && + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sel->r.height = isc->scaler_format[ISC_SCALER_PAD_SOURCE].height; + sel->r.width = isc->scaler_format[ISC_SCALER_PAD_SOURCE].width; + + sel->r.left = 0; + sel->r.top = 0; + + return 0; +} + +static int isc_scaler_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *v4l2_try_fmt = + v4l2_subdev_get_try_format(sd, sd_state, 0); + struct v4l2_rect *try_crop; + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + + *v4l2_try_fmt = isc->scaler_format[ISC_SCALER_PAD_SOURCE]; + + try_crop = v4l2_subdev_get_try_crop(sd, sd_state, 0); + + try_crop->top = 0; + try_crop->left = 0; + try_crop->width = v4l2_try_fmt->width; + try_crop->height = v4l2_try_fmt->height; + + return 0; +} + +static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = { + .enum_mbus_code = isc_scaler_enum_mbus_code, + .set_fmt = isc_scaler_set_fmt, + .get_fmt = isc_scaler_get_fmt, + .get_selection = isc_scaler_g_sel, + .init_cfg = isc_scaler_init_cfg, +}; + +static const struct media_entity_operations isc_scaler_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = { + .pad = &isc_scaler_pad_ops, +}; + +int isc_scaler_init(struct isc_device *isc) +{ + int ret; + + v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops); + + isc->scaler_sd.owner = THIS_MODULE; + isc->scaler_sd.dev = isc->dev; + snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name), + "microchip_isc_scaler"); + + isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + isc->scaler_sd.entity.ops = &isc_scaler_entity_ops; + isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + isc_scaler_prepare_fmt(&isc->scaler_format[ISC_SCALER_PAD_SOURCE]); + isc->scaler_format[ISC_SCALER_PAD_SOURCE].height = isc->max_height; + isc->scaler_format[ISC_SCALER_PAD_SOURCE].width = isc->max_width; + isc->scaler_format[ISC_SCALER_PAD_SOURCE].code = + isc->formats_list[0].mbus_code; + + isc->scaler_format[ISC_SCALER_PAD_SINK] = + isc->scaler_format[ISC_SCALER_PAD_SOURCE]; + + ret = media_entity_pads_init(&isc->scaler_sd.entity, + ISC_SCALER_PADS_NUM, + isc->scaler_pads); + if (ret < 0) { + dev_err(isc->dev, "scaler sd media entity init failed\n"); + return ret; + } + + ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd); + if (ret < 0) { + dev_err(isc->dev, "scaler sd failed to register subdev\n"); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(isc_scaler_init); + +int isc_scaler_link(struct isc_device *isc) +{ + int ret; + + ret = media_create_pad_link(&isc->current_subdev->sd->entity, + isc->remote_pad, &isc->scaler_sd.entity, + ISC_SCALER_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + + if (ret < 0) { + dev_err(isc->dev, "Failed to create pad link: %s to %s\n", + isc->current_subdev->sd->entity.name, + isc->scaler_sd.entity.name); + return ret; + } + + dev_dbg(isc->dev, "link with %s pad: %d\n", + isc->current_subdev->sd->name, isc->remote_pad); + + ret = media_create_pad_link(&isc->scaler_sd.entity, + ISC_SCALER_PAD_SOURCE, + &isc->video_dev.entity, ISC_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + + if (ret < 0) { + dev_err(isc->dev, "Failed to create pad link: %s to %s\n", + isc->scaler_sd.entity.name, + isc->video_dev.entity.name); + return ret; + } + + dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name, + ISC_SCALER_PAD_SOURCE); + + return ret; +} +EXPORT_SYMBOL_GPL(isc_scaler_link); + diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h new file mode 100644 index 000000000000..e3a6c7367e70 --- /dev/null +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -0,0 +1,400 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Microchip Image Sensor Controller (ISC) driver header file + * + * Copyright (C) 2016-2019 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev <eugen.hristev@microchip.com> + * + */ +#ifndef _MICROCHIP_ISC_H_ + +#include <linux/clk-provider.h> +#include <linux/platform_device.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/videobuf2-dma-contig.h> + +#define ISC_CLK_MAX_DIV 255 + +enum isc_clk_id { + ISC_ISPCK = 0, + ISC_MCK = 1, +}; + +struct isc_clk { + struct clk_hw hw; + struct clk *clk; + struct regmap *regmap; + spinlock_t lock; /* serialize access to clock registers */ + u8 id; + u8 parent_id; + u32 div; + struct device *dev; +}; + +#define to_isc_clk(v) container_of(v, struct isc_clk, hw) + +struct isc_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct isc_subdev_entity { + struct v4l2_subdev *sd; + struct v4l2_async_subdev *asd; + struct device_node *epn; + struct v4l2_async_notifier notifier; + + u32 pfe_cfg0; + + struct list_head list; +}; + +/* + * struct isc_format - ISC media bus format information + This structure represents the interface between the ISC + and the sensor. It's the input format received by + the ISC. + * @fourcc: Fourcc code for this format + * @mbus_code: V4L2 media bus format code. + * @cfa_baycfg: If this format is RAW BAYER, indicate the type of bayer. + this is either BGBG, RGRG, etc. + * @pfe_cfg0_bps: Number of hardware data lines connected to the ISC + * @raw: If the format is raw bayer. + */ + +struct isc_format { + u32 fourcc; + u32 mbus_code; + u32 cfa_baycfg; + u32 pfe_cfg0_bps; + + bool raw; +}; + +/* Pipeline bitmap */ +#define DPC_DPCENABLE BIT(0) +#define DPC_GDCENABLE BIT(1) +#define DPC_BLCENABLE BIT(2) +#define WB_ENABLE BIT(3) +#define CFA_ENABLE BIT(4) +#define CC_ENABLE BIT(5) +#define GAM_ENABLE BIT(6) +#define GAM_BENABLE BIT(7) +#define GAM_GENABLE BIT(8) +#define GAM_RENABLE BIT(9) +#define VHXS_ENABLE BIT(10) +#define CSC_ENABLE BIT(11) +#define CBC_ENABLE BIT(12) +#define SUB422_ENABLE BIT(13) +#define SUB420_ENABLE BIT(14) + +#define GAM_ENABLES (GAM_RENABLE | GAM_GENABLE | GAM_BENABLE | GAM_ENABLE) + +/* + * struct fmt_config - ISC format configuration and internal pipeline + This structure represents the internal configuration + of the ISC. + It also holds the format that ISC will present to v4l2. + * @sd_format: Pointer to an isc_format struct that holds the sensor + configuration. + * @fourcc: Fourcc code for this format. + * @bpp: Bytes per pixel in the current format. + * @bpp_v4l2: Bytes per pixel in the current format, for v4l2. + This differs from 'bpp' in the sense that in planar + formats, it refers only to the first plane. + * @rlp_cfg_mode: Configuration of the RLP (rounding, limiting packaging) + * @dcfg_imode: Configuration of the input of the DMA module + * @dctrl_dview: Configuration of the output of the DMA module + * @bits_pipeline: Configuration of the pipeline, which modules are enabled + */ +struct fmt_config { + struct isc_format *sd_format; + + u32 fourcc; + u8 bpp; + u8 bpp_v4l2; + + u32 rlp_cfg_mode; + u32 dcfg_imode; + u32 dctrl_dview; + + u32 bits_pipeline; +}; + +#define HIST_ENTRIES 512 +#define HIST_BAYER (ISC_HIS_CFG_MODE_B + 1) + +enum{ + HIST_INIT = 0, + HIST_ENABLED, + HIST_DISABLED, +}; + +struct isc_ctrls { + struct v4l2_ctrl_handler handler; + + u32 brightness; + u32 contrast; + u8 gamma_index; +#define ISC_WB_NONE 0 +#define ISC_WB_AUTO 1 +#define ISC_WB_ONETIME 2 + u8 awb; + + /* one for each component : GR, R, GB, B */ + u32 gain[HIST_BAYER]; + s32 offset[HIST_BAYER]; + + u32 hist_entry[HIST_ENTRIES]; + u32 hist_count[HIST_BAYER]; + u8 hist_id; + u8 hist_stat; +#define HIST_MIN_INDEX 0 +#define HIST_MAX_INDEX 1 + u32 hist_minmax[HIST_BAYER][2]; +}; + +#define ISC_PIPE_LINE_NODE_NUM 15 + +/* + * struct isc_reg_offsets - ISC device register offsets + * @csc: Offset for the CSC register + * @cbc: Offset for the CBC register + * @sub422: Offset for the SUB422 register + * @sub420: Offset for the SUB420 register + * @rlp: Offset for the RLP register + * @his: Offset for the HIS related registers + * @dma: Offset for the DMA related registers + * @version: Offset for the version register + * @his_entry: Offset for the HIS entries registers + */ +struct isc_reg_offsets { + u32 csc; + u32 cbc; + u32 sub422; + u32 sub420; + u32 rlp; + u32 his; + u32 dma; + u32 version; + u32 his_entry; +}; + +enum isc_mc_pads { + ISC_PAD_SINK = 0, + ISC_PADS_NUM = 1, +}; + +enum isc_scaler_pads { + ISC_SCALER_PAD_SINK = 0, + ISC_SCALER_PAD_SOURCE = 1, + ISC_SCALER_PADS_NUM = 2, +}; + +/* + * struct isc_device - ISC device driver data/config struct + * @regmap: Register map + * @hclock: Hclock clock input (refer datasheet) + * @ispck: iscpck clock (refer datasheet) + * @isc_clks: ISC clocks + * @ispck_required: ISC requires ISP Clock initialization + * @dcfg: DMA master configuration, architecture dependent + * + * @dev: Registered device driver + * @v4l2_dev: v4l2 registered device + * @video_dev: registered video device + * + * @vb2_vidq: video buffer 2 video queue + * @dma_queue_lock: lock to serialize the dma buffer queue + * @dma_queue: the queue for dma buffers + * @cur_frm: current isc frame/buffer + * @sequence: current frame number + * @stop: true if isc is not streaming, false if streaming + * @comp: completion reference that signals frame completion + * + * @fmt: current v42l format + * @try_fmt: current v4l2 try format + * + * @config: current ISC format configuration + * @try_config: the current ISC try format , not yet activated + * + * @ctrls: holds information about ISC controls + * @do_wb_ctrl: control regarding the DO_WHITE_BALANCE button + * @awb_work: workqueue reference for autowhitebalance histogram + * analysis + * + * @lock: lock for serializing userspace file operations + * with ISC operations + * @awb_mutex: serialize access to streaming status from awb work queue + * @awb_lock: lock for serializing awb work queue operations + * with DMA/buffer operations + * + * @pipeline: configuration of the ISC pipeline + * + * @current_subdev: current subdevice: the sensor + * @subdev_entities: list of subdevice entitites + * + * @gamma_table: pointer to the table with gamma values, has + * gamma_max sets of GAMMA_ENTRIES entries each + * @gamma_max: maximum number of sets of inside the gamma_table + * + * @max_width: maximum frame width, dependent on the internal RAM + * @max_height: maximum frame height, dependent on the internal RAM + * + * @config_dpc: pointer to a function that initializes product + * specific DPC module + * @config_csc: pointer to a function that initializes product + * specific CSC module + * @config_cbc: pointer to a function that initializes product + * specific CBC module + * @config_cc: pointer to a function that initializes product + * specific CC module + * @config_gam: pointer to a function that initializes product + * specific GAMMA module + * @config_rlp: pointer to a function that initializes product + * specific RLP module + * @config_ctrls: pointer to a functoin that initializes product + * specific v4l2 controls. + * + * @adapt_pipeline: pointer to a function that adapts the pipeline bits + * to the product specific pipeline + * + * @offsets: struct holding the product specific register offsets + * @controller_formats: pointer to the array of possible formats that the + * controller can output + * @formats_list: pointer to the array of possible formats that can + * be used as an input to the controller + * @controller_formats_size: size of controller_formats array + * @formats_list_size: size of formats_list array + * @pads: media controller pads for isc video entity + * @mdev: media device that is registered by the isc + * @mpipe: media device pipeline used by the isc + * @remote_pad: remote pad on the connected subdevice + * @scaler_sd: subdevice for the scaler that isc registers + * @scaler_pads: media controller pads for the scaler subdevice + * @scaler_format: current format for the scaler subdevice + */ +struct isc_device { + struct regmap *regmap; + struct clk *hclock; + struct clk *ispck; + struct isc_clk isc_clks[2]; + bool ispck_required; + u32 dcfg; + + struct device *dev; + struct v4l2_device v4l2_dev; + struct video_device video_dev; + + struct vb2_queue vb2_vidq; + spinlock_t dma_queue_lock; + struct list_head dma_queue; + struct isc_buffer *cur_frm; + unsigned int sequence; + bool stop; + struct completion comp; + + struct v4l2_format fmt; + struct v4l2_format try_fmt; + + struct fmt_config config; + struct fmt_config try_config; + + struct isc_ctrls ctrls; + struct work_struct awb_work; + + struct mutex lock; + struct mutex awb_mutex; + spinlock_t awb_lock; + + struct regmap_field *pipeline[ISC_PIPE_LINE_NODE_NUM]; + + struct isc_subdev_entity *current_subdev; + struct list_head subdev_entities; + + struct { +#define ISC_CTRL_DO_WB 1 +#define ISC_CTRL_R_GAIN 2 +#define ISC_CTRL_B_GAIN 3 +#define ISC_CTRL_GR_GAIN 4 +#define ISC_CTRL_GB_GAIN 5 +#define ISC_CTRL_R_OFF 6 +#define ISC_CTRL_B_OFF 7 +#define ISC_CTRL_GR_OFF 8 +#define ISC_CTRL_GB_OFF 9 + struct v4l2_ctrl *awb_ctrl; + struct v4l2_ctrl *do_wb_ctrl; + struct v4l2_ctrl *r_gain_ctrl; + struct v4l2_ctrl *b_gain_ctrl; + struct v4l2_ctrl *gr_gain_ctrl; + struct v4l2_ctrl *gb_gain_ctrl; + struct v4l2_ctrl *r_off_ctrl; + struct v4l2_ctrl *b_off_ctrl; + struct v4l2_ctrl *gr_off_ctrl; + struct v4l2_ctrl *gb_off_ctrl; + }; + +#define GAMMA_ENTRIES 64 + /* pointer to the defined gamma table */ + const u32 (*gamma_table)[GAMMA_ENTRIES]; + u32 gamma_max; + + u32 max_width; + u32 max_height; + + struct { + void (*config_dpc)(struct isc_device *isc); + void (*config_csc)(struct isc_device *isc); + void (*config_cbc)(struct isc_device *isc); + void (*config_cc)(struct isc_device *isc); + void (*config_gam)(struct isc_device *isc); + void (*config_rlp)(struct isc_device *isc); + + void (*config_ctrls)(struct isc_device *isc, + const struct v4l2_ctrl_ops *ops); + + void (*adapt_pipeline)(struct isc_device *isc); + }; + + struct isc_reg_offsets offsets; + const struct isc_format *controller_formats; + struct isc_format *formats_list; + u32 controller_formats_size; + u32 formats_list_size; + + struct { + struct media_pad pads[ISC_PADS_NUM]; + struct media_device mdev; + struct media_pipeline mpipe; + + u32 remote_pad; + }; + + struct { + struct v4l2_subdev scaler_sd; + struct media_pad scaler_pads[ISC_SCALER_PADS_NUM]; + struct v4l2_mbus_framefmt scaler_format[ISC_SCALER_PADS_NUM]; + }; +}; + +extern const struct regmap_config microchip_isc_regmap_config; +extern const struct v4l2_async_notifier_operations microchip_isc_async_ops; + +irqreturn_t microchip_isc_interrupt(int irq, void *dev_id); +int microchip_isc_pipeline_init(struct isc_device *isc); +int microchip_isc_clk_init(struct isc_device *isc); +void microchip_isc_subdev_cleanup(struct isc_device *isc); +void microchip_isc_clk_cleanup(struct isc_device *isc); + +int isc_scaler_link(struct isc_device *isc); +int isc_scaler_init(struct isc_device *isc); +int isc_mc_init(struct isc_device *isc, u32 ver); +void isc_mc_cleanup(struct isc_device *isc); + +struct isc_format *isc_find_format_by_code(struct isc_device *isc, + unsigned int code, int *index); +#endif diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c new file mode 100644 index 000000000000..ac4715d91de6 --- /dev/null +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -0,0 +1,683 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip Image Sensor Controller (ISC) driver + * + * Copyright (C) 2016-2019 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev <eugen.hristev@microchip.com> + * + * + * Sensor-->PFE-->WB-->CFA-->CC-->GAM-->CSC-->CBC-->SUB-->RLP-->DMA + * + * ISC video pipeline integrates the following submodules: + * PFE: Parallel Front End to sample the camera sensor input stream + * WB: Programmable white balance in the Bayer domain + * CFA: Color filter array interpolation module + * CC: Programmable color correction + * GAM: Gamma correction + * CSC: Programmable color space conversion + * CBC: Contrast and Brightness control + * SUB: This module performs YCbCr444 to YCbCr420 chrominance subsampling + * RLP: This module performs rounding, range limiting + * and packing of the incoming data + */ + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> + +#include "microchip-isc-regs.h" +#include "microchip-isc.h" + +#define ISC_SAMA5D2_MAX_SUPPORT_WIDTH 2592 +#define ISC_SAMA5D2_MAX_SUPPORT_HEIGHT 1944 + +#define ISC_SAMA5D2_PIPELINE \ + (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ + CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) + +/* This is a list of the formats that the ISC can *output* */ +static const struct isc_format sama5d2_controller_formats[] = { + { + .fourcc = V4L2_PIX_FMT_ARGB444, + }, { + .fourcc = V4L2_PIX_FMT_ARGB555, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + }, { + .fourcc = V4L2_PIX_FMT_XBGR32, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + }, { + .fourcc = V4L2_PIX_FMT_Y10, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .raw = true, + }, +}; + +/* This is a list of formats that the ISC can receive as *input* */ +static struct isc_format sama5d2_formats_list[] = { + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_GREY, + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_Y10, + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + }, + +}; + +static void isc_sama5d2_config_csc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Convert RGB to YUV */ + regmap_write(regmap, ISC_CSC_YR_YG + isc->offsets.csc, + 0x42 | (0x81 << 16)); + regmap_write(regmap, ISC_CSC_YB_OY + isc->offsets.csc, + 0x19 | (0x10 << 16)); + regmap_write(regmap, ISC_CSC_CBR_CBG + isc->offsets.csc, + 0xFDA | (0xFB6 << 16)); + regmap_write(regmap, ISC_CSC_CBB_OCB + isc->offsets.csc, + 0x70 | (0x80 << 16)); + regmap_write(regmap, ISC_CSC_CRR_CRG + isc->offsets.csc, + 0x70 | (0xFA2 << 16)); + regmap_write(regmap, ISC_CSC_CRB_OCR + isc->offsets.csc, + 0xFEE | (0x80 << 16)); +} + +static void isc_sama5d2_config_cbc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + regmap_write(regmap, ISC_CBC_BRIGHT + isc->offsets.cbc, + isc->ctrls.brightness); + regmap_write(regmap, ISC_CBC_CONTRAST + isc->offsets.cbc, + isc->ctrls.contrast); +} + +static void isc_sama5d2_config_cc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Configure each register at the neutral fixed point 1.0 or 0.0 */ + regmap_write(regmap, ISC_CC_RR_RG, (1 << 8)); + regmap_write(regmap, ISC_CC_RB_OR, 0); + regmap_write(regmap, ISC_CC_GR_GG, (1 << 8) << 16); + regmap_write(regmap, ISC_CC_GB_OG, 0); + regmap_write(regmap, ISC_CC_BR_BG, 0); + regmap_write(regmap, ISC_CC_BB_OB, (1 << 8)); +} + +static void isc_sama5d2_config_ctrls(struct isc_device *isc, + const struct v4l2_ctrl_ops *ops) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + + ctrls->contrast = 256; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -2048, 2047, 1, 256); +} + +static void isc_sama5d2_config_dpc(struct isc_device *isc) +{ + /* This module is not present on sama5d2 pipeline */ +} + +static void isc_sama5d2_config_gam(struct isc_device *isc) +{ + /* No specific gamma configuration */ +} + +static void isc_sama5d2_config_rlp(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 rlp_mode = isc->config.rlp_cfg_mode; + + /* + * In sama5d2, the YUV planar modes and the YUYV modes are treated + * in the same way in RLP register. + * Normally, YYCC mode should be Luma(n) - Color B(n) - Color R (n) + * and YCYC should be Luma(n + 1) - Color B (n) - Luma (n) - Color R (n) + * but in sama5d2, the YCYC mode does not exist, and YYCC must be + * selected for both planar and interleaved modes, as in fact + * both modes are supported. + * + * Thus, if the YCYC mode is selected, replace it with the + * sama5d2-compliant mode which is YYCC . + */ + if ((rlp_mode & ISC_RLP_CFG_MODE_MASK) == ISC_RLP_CFG_MODE_YCYC) { + rlp_mode &= ~ISC_RLP_CFG_MODE_MASK; + rlp_mode |= ISC_RLP_CFG_MODE_YYCC; + } + + regmap_update_bits(regmap, ISC_RLP_CFG + isc->offsets.rlp, + ISC_RLP_CFG_MODE_MASK, rlp_mode); +} + +static void isc_sama5d2_adapt_pipeline(struct isc_device *isc) +{ + isc->try_config.bits_pipeline &= ISC_SAMA5D2_PIPELINE; +} + +/* Gamma table with gamma 1/2.2 */ +static const u32 isc_sama5d2_gamma_table[][GAMMA_ENTRIES] = { + /* 0 --> gamma 1/1.8 */ + { 0x65, 0x66002F, 0x950025, 0xBB0020, 0xDB001D, 0xF8001A, + 0x1130018, 0x12B0017, 0x1420016, 0x1580014, 0x16D0013, 0x1810012, + 0x1940012, 0x1A60012, 0x1B80011, 0x1C90010, 0x1DA0010, 0x1EA000F, + 0x1FA000F, 0x209000F, 0x218000F, 0x227000E, 0x235000E, 0x243000E, + 0x251000E, 0x25F000D, 0x26C000D, 0x279000D, 0x286000D, 0x293000C, + 0x2A0000C, 0x2AC000C, 0x2B8000C, 0x2C4000C, 0x2D0000B, 0x2DC000B, + 0x2E7000B, 0x2F3000B, 0x2FE000B, 0x309000B, 0x314000B, 0x31F000A, + 0x32A000A, 0x334000B, 0x33F000A, 0x349000A, 0x354000A, 0x35E000A, + 0x368000A, 0x372000A, 0x37C000A, 0x386000A, 0x3900009, 0x399000A, + 0x3A30009, 0x3AD0009, 0x3B60009, 0x3BF000A, 0x3C90009, 0x3D20009, + 0x3DB0009, 0x3E40009, 0x3ED0009, 0x3F60009 }, + + /* 1 --> gamma 1/2 */ + { 0x7F, 0x800034, 0xB50028, 0xDE0021, 0x100001E, 0x11E001B, + 0x1390019, 0x1520017, 0x16A0015, 0x1800014, 0x1940014, 0x1A80013, + 0x1BB0012, 0x1CD0011, 0x1DF0010, 0x1EF0010, 0x200000F, 0x20F000F, + 0x21F000E, 0x22D000F, 0x23C000E, 0x24A000E, 0x258000D, 0x265000D, + 0x273000C, 0x27F000D, 0x28C000C, 0x299000C, 0x2A5000C, 0x2B1000B, + 0x2BC000C, 0x2C8000B, 0x2D3000C, 0x2DF000B, 0x2EA000A, 0x2F5000A, + 0x2FF000B, 0x30A000A, 0x314000B, 0x31F000A, 0x329000A, 0x333000A, + 0x33D0009, 0x3470009, 0x350000A, 0x35A0009, 0x363000A, 0x36D0009, + 0x3760009, 0x37F0009, 0x3880009, 0x3910009, 0x39A0009, 0x3A30009, + 0x3AC0008, 0x3B40009, 0x3BD0008, 0x3C60008, 0x3CE0008, 0x3D60009, + 0x3DF0008, 0x3E70008, 0x3EF0008, 0x3F70008 }, + + /* 2 --> gamma 1/2.2 */ + { 0x99, 0x9B0038, 0xD4002A, 0xFF0023, 0x122001F, 0x141001B, + 0x15D0019, 0x1760017, 0x18E0015, 0x1A30015, 0x1B80013, 0x1CC0012, + 0x1DE0011, 0x1F00010, 0x2010010, 0x2110010, 0x221000F, 0x230000F, + 0x23F000E, 0x24D000E, 0x25B000D, 0x269000C, 0x276000C, 0x283000C, + 0x28F000C, 0x29B000C, 0x2A7000C, 0x2B3000B, 0x2BF000B, 0x2CA000B, + 0x2D5000B, 0x2E0000A, 0x2EB000A, 0x2F5000A, 0x2FF000A, 0x30A000A, + 0x3140009, 0x31E0009, 0x327000A, 0x3310009, 0x33A0009, 0x3440009, + 0x34D0009, 0x3560009, 0x35F0009, 0x3680008, 0x3710008, 0x3790009, + 0x3820008, 0x38A0008, 0x3930008, 0x39B0008, 0x3A30008, 0x3AB0008, + 0x3B30008, 0x3BB0008, 0x3C30008, 0x3CB0007, 0x3D20008, 0x3DA0007, + 0x3E20007, 0x3E90007, 0x3F00008, 0x3F80007 }, +}; + +static int isc_parse_dt(struct device *dev, struct isc_device *isc) +{ + struct device_node *np = dev->of_node; + struct device_node *epn = NULL; + struct isc_subdev_entity *subdev_entity; + unsigned int flags; + int ret; + + INIT_LIST_HEAD(&isc->subdev_entities); + + while (1) { + struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; + + epn = of_graph_get_next_endpoint(np, epn); + if (!epn) + return 0; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), + &v4l2_epn); + if (ret) { + ret = -EINVAL; + dev_err(dev, "Could not parse the endpoint\n"); + break; + } + + subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), + GFP_KERNEL); + if (!subdev_entity) { + ret = -ENOMEM; + break; + } + subdev_entity->epn = epn; + + flags = v4l2_epn.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 = ISC_PFE_CFG0_HPOL_LOW; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_VPOL_LOW; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_PPOL_LOW; + + if (v4l2_epn.bus_type == V4L2_MBUS_BT656) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_CCIR_CRC | + ISC_PFE_CFG0_CCIR656; + + list_add_tail(&subdev_entity->list, &isc->subdev_entities); + } + of_node_put(epn); + + return ret; +} + +static int microchip_isc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct isc_device *isc; + struct resource *res; + void __iomem *io_base; + struct isc_subdev_entity *subdev_entity; + int irq; + int ret; + u32 ver; + + isc = devm_kzalloc(dev, sizeof(*isc), GFP_KERNEL); + if (!isc) + return -ENOMEM; + + platform_set_drvdata(pdev, isc); + isc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + isc->regmap = devm_regmap_init_mmio(dev, io_base, µchip_isc_regmap_config); + if (IS_ERR(isc->regmap)) { + ret = PTR_ERR(isc->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, + "microchip-sama5d2-isc", isc); + if (ret < 0) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + irq, ret); + return ret; + } + + isc->gamma_table = isc_sama5d2_gamma_table; + isc->gamma_max = 2; + + isc->max_width = ISC_SAMA5D2_MAX_SUPPORT_WIDTH; + isc->max_height = ISC_SAMA5D2_MAX_SUPPORT_HEIGHT; + + isc->config_dpc = isc_sama5d2_config_dpc; + isc->config_csc = isc_sama5d2_config_csc; + isc->config_cbc = isc_sama5d2_config_cbc; + isc->config_cc = isc_sama5d2_config_cc; + isc->config_gam = isc_sama5d2_config_gam; + isc->config_rlp = isc_sama5d2_config_rlp; + isc->config_ctrls = isc_sama5d2_config_ctrls; + + isc->adapt_pipeline = isc_sama5d2_adapt_pipeline; + + isc->offsets.csc = ISC_SAMA5D2_CSC_OFFSET; + isc->offsets.cbc = ISC_SAMA5D2_CBC_OFFSET; + isc->offsets.sub422 = ISC_SAMA5D2_SUB422_OFFSET; + isc->offsets.sub420 = ISC_SAMA5D2_SUB420_OFFSET; + isc->offsets.rlp = ISC_SAMA5D2_RLP_OFFSET; + isc->offsets.his = ISC_SAMA5D2_HIS_OFFSET; + isc->offsets.dma = ISC_SAMA5D2_DMA_OFFSET; + isc->offsets.version = ISC_SAMA5D2_VERSION_OFFSET; + isc->offsets.his_entry = ISC_SAMA5D2_HIS_ENTRY_OFFSET; + + isc->controller_formats = sama5d2_controller_formats; + isc->controller_formats_size = ARRAY_SIZE(sama5d2_controller_formats); + isc->formats_list = sama5d2_formats_list; + isc->formats_list_size = ARRAY_SIZE(sama5d2_formats_list); + + /* sama5d2-isc - 8 bits per beat */ + isc->dcfg = ISC_DCFG_YMBSIZE_BEATS8 | ISC_DCFG_CMBSIZE_BEATS8; + + /* sama5d2-isc : ISPCK is required and mandatory */ + isc->ispck_required = true; + + ret = microchip_isc_pipeline_init(isc); + if (ret) + return ret; + + isc->hclock = devm_clk_get(dev, "hclock"); + if (IS_ERR(isc->hclock)) { + ret = PTR_ERR(isc->hclock); + dev_err(dev, "failed to get hclock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(isc->hclock); + if (ret) { + dev_err(dev, "failed to enable hclock: %d\n", ret); + return ret; + } + + ret = microchip_isc_clk_init(isc); + if (ret) { + dev_err(dev, "failed to init isc clock: %d\n", ret); + goto unprepare_hclk; + } + ret = v4l2_device_register(dev, &isc->v4l2_dev); + if (ret) { + dev_err(dev, "unable to register v4l2 device.\n"); + goto unprepare_clk; + } + + ret = isc_parse_dt(dev, isc); + if (ret) { + dev_err(dev, "fail to parse device tree\n"); + goto unregister_v4l2_device; + } + + if (list_empty(&isc->subdev_entities)) { + dev_err(dev, "no subdev found\n"); + ret = -ENODEV; + goto unregister_v4l2_device; + } + + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode = + of_fwnode_handle(subdev_entity->epn); + + v4l2_async_nf_init(&subdev_entity->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&subdev_entity->notifier, + fwnode, + struct v4l2_async_subdev); + + of_node_put(subdev_entity->epn); + subdev_entity->epn = NULL; + + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto cleanup_subdev; + } + + subdev_entity->notifier.ops = µchip_isc_async_ops; + + ret = v4l2_async_nf_register(&isc->v4l2_dev, + &subdev_entity->notifier); + if (ret) { + dev_err(dev, "fail to register async notifier\n"); + goto cleanup_subdev; + } + + if (video_is_registered(&isc->video_dev)) + break; + } + + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); + + ret = isc_mc_init(isc, ver); + if (ret < 0) + goto isc_probe_mc_init_err; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_request_idle(dev); + + isc->ispck = isc->isc_clks[ISC_ISPCK].clk; + + ret = clk_prepare_enable(isc->ispck); + if (ret) { + dev_err(dev, "failed to enable ispck: %d\n", ret); + goto disable_pm; + } + + /* ispck should be greater or equal to hclock */ + ret = clk_set_rate(isc->ispck, clk_get_rate(isc->hclock)); + if (ret) { + dev_err(dev, "failed to set ispck rate: %d\n", ret); + goto unprepare_clk; + } + + dev_info(dev, "Microchip ISC version %x\n", ver); + + return 0; + +unprepare_clk: + clk_disable_unprepare(isc->ispck); + +disable_pm: + pm_runtime_disable(dev); + +isc_probe_mc_init_err: + isc_mc_cleanup(isc); + +cleanup_subdev: + microchip_isc_subdev_cleanup(isc); + +unregister_v4l2_device: + v4l2_device_unregister(&isc->v4l2_dev); + +unprepare_hclk: + clk_disable_unprepare(isc->hclock); + + microchip_isc_clk_cleanup(isc); + + return ret; +} + +static int microchip_isc_remove(struct platform_device *pdev) +{ + struct isc_device *isc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + isc_mc_cleanup(isc); + + microchip_isc_subdev_cleanup(isc); + + v4l2_device_unregister(&isc->v4l2_dev); + + clk_disable_unprepare(isc->ispck); + clk_disable_unprepare(isc->hclock); + + microchip_isc_clk_cleanup(isc); + + return 0; +} + +static int __maybe_unused isc_runtime_suspend(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + + clk_disable_unprepare(isc->ispck); + clk_disable_unprepare(isc->hclock); + + return 0; +} + +static int __maybe_unused isc_runtime_resume(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(isc->hclock); + if (ret) + return ret; + + ret = clk_prepare_enable(isc->ispck); + if (ret) + clk_disable_unprepare(isc->hclock); + + return ret; +} + +static const struct dev_pm_ops microchip_isc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(isc_runtime_suspend, isc_runtime_resume, NULL) +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id microchip_isc_of_match[] = { + { .compatible = "atmel,sama5d2-isc" }, + { } +}; +MODULE_DEVICE_TABLE(of, microchip_isc_of_match); +#endif + +static struct platform_driver microchip_isc_driver = { + .probe = microchip_isc_probe, + .remove = microchip_isc_remove, + .driver = { + .name = "microchip-sama5d2-isc", + .pm = µchip_isc_dev_pm_ops, + .of_match_table = of_match_ptr(microchip_isc_of_match), + }, +}; + +module_platform_driver(microchip_isc_driver); + +MODULE_AUTHOR("Songjun Wu"); +MODULE_DESCRIPTION("The V4L2 driver for Microchip-ISC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c new file mode 100644 index 000000000000..d583eafe5cc1 --- /dev/null +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip eXtended Image Sensor Controller (XISC) driver + * + * Copyright (C) 2019-2021 Microchip Technology, Inc. and its subsidiaries + * + * Author: Eugen Hristev <eugen.hristev@microchip.com> + * + * Sensor-->PFE-->DPC-->WB-->CFA-->CC-->GAM-->VHXS-->CSC-->CBHS-->SUB-->RLP-->DMA-->HIS + * + * ISC video pipeline integrates the following submodules: + * PFE: Parallel Front End to sample the camera sensor input stream + * DPC: Defective Pixel Correction with black offset correction, green disparity + * correction and defective pixel correction (3 modules total) + * WB: Programmable white balance in the Bayer domain + * CFA: Color filter array interpolation module + * CC: Programmable color correction + * GAM: Gamma correction + *VHXS: Vertical and Horizontal Scaler + * CSC: Programmable color space conversion + *CBHS: Contrast Brightness Hue and Saturation control + * SUB: This module performs YCbCr444 to YCbCr420 chrominance subsampling + * RLP: This module performs rounding, range limiting + * and packing of the incoming data + * DMA: This module performs DMA master accesses to write frames to external RAM + * HIS: Histogram module performs statistic counters on the frames + */ + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> + +#include "microchip-isc-regs.h" +#include "microchip-isc.h" + +#define ISC_SAMA7G5_MAX_SUPPORT_WIDTH 3264 +#define ISC_SAMA7G5_MAX_SUPPORT_HEIGHT 2464 + +#define ISC_SAMA7G5_PIPELINE \ + (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ + CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) + +/* This is a list of the formats that the ISC can *output* */ +static const struct isc_format sama7g5_controller_formats[] = { + { + .fourcc = V4L2_PIX_FMT_ARGB444, + }, { + .fourcc = V4L2_PIX_FMT_ARGB555, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + }, { + .fourcc = V4L2_PIX_FMT_XBGR32, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + }, { + .fourcc = V4L2_PIX_FMT_Y10, + }, { + .fourcc = V4L2_PIX_FMT_Y16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .raw = true, + }, +}; + +/* This is a list of formats that the ISC can receive as *input* */ +static struct isc_format sama7g5_formats_list[] = { + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_GREY, + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_Y10, + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + }, +}; + +static void isc_sama7g5_config_csc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Convert RGB to YUV */ + regmap_write(regmap, ISC_CSC_YR_YG + isc->offsets.csc, + 0x42 | (0x81 << 16)); + regmap_write(regmap, ISC_CSC_YB_OY + isc->offsets.csc, + 0x19 | (0x10 << 16)); + regmap_write(regmap, ISC_CSC_CBR_CBG + isc->offsets.csc, + 0xFDA | (0xFB6 << 16)); + regmap_write(regmap, ISC_CSC_CBB_OCB + isc->offsets.csc, + 0x70 | (0x80 << 16)); + regmap_write(regmap, ISC_CSC_CRR_CRG + isc->offsets.csc, + 0x70 | (0xFA2 << 16)); + regmap_write(regmap, ISC_CSC_CRB_OCR + isc->offsets.csc, + 0xFEE | (0x80 << 16)); +} + +static void isc_sama7g5_config_cbc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Configure what is set via v4l2 ctrls */ + regmap_write(regmap, ISC_CBC_BRIGHT + isc->offsets.cbc, isc->ctrls.brightness); + regmap_write(regmap, ISC_CBC_CONTRAST + isc->offsets.cbc, isc->ctrls.contrast); + /* Configure Hue and Saturation as neutral midpoint */ + regmap_write(regmap, ISC_CBCHS_HUE, 0); + regmap_write(regmap, ISC_CBCHS_SAT, (1 << 4)); +} + +static void isc_sama7g5_config_cc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Configure each register at the neutral fixed point 1.0 or 0.0 */ + regmap_write(regmap, ISC_CC_RR_RG, (1 << 8)); + regmap_write(regmap, ISC_CC_RB_OR, 0); + regmap_write(regmap, ISC_CC_GR_GG, (1 << 8) << 16); + regmap_write(regmap, ISC_CC_GB_OG, 0); + regmap_write(regmap, ISC_CC_BR_BG, 0); + regmap_write(regmap, ISC_CC_BB_OB, (1 << 8)); +} + +static void isc_sama7g5_config_ctrls(struct isc_device *isc, + const struct v4l2_ctrl_ops *ops) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + + ctrls->contrast = 16; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -2048, 2047, 1, 16); +} + +static void isc_sama7g5_config_dpc(struct isc_device *isc) +{ + u32 bay_cfg = isc->config.sd_format->cfa_baycfg; + struct regmap *regmap = isc->regmap; + + regmap_update_bits(regmap, ISC_DPC_CFG, ISC_DPC_CFG_BLOFF_MASK, + (64 << ISC_DPC_CFG_BLOFF_SHIFT)); + regmap_update_bits(regmap, ISC_DPC_CFG, ISC_DPC_CFG_BAYCFG_MASK, + (bay_cfg << ISC_DPC_CFG_BAYCFG_SHIFT)); +} + +static void isc_sama7g5_config_gam(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + regmap_update_bits(regmap, ISC_GAM_CTRL, ISC_GAM_CTRL_BIPART, + ISC_GAM_CTRL_BIPART); +} + +static void isc_sama7g5_config_rlp(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 rlp_mode = isc->config.rlp_cfg_mode; + + regmap_update_bits(regmap, ISC_RLP_CFG + isc->offsets.rlp, + ISC_RLP_CFG_MODE_MASK | ISC_RLP_CFG_LSH | + ISC_RLP_CFG_YMODE_MASK, rlp_mode); +} + +static void isc_sama7g5_adapt_pipeline(struct isc_device *isc) +{ + isc->try_config.bits_pipeline &= ISC_SAMA7G5_PIPELINE; +} + +/* Gamma table with gamma 1/2.2 */ +static const u32 isc_sama7g5_gamma_table[][GAMMA_ENTRIES] = { + /* index 0 --> gamma bipartite */ + { + 0x980, 0x4c0320, 0x650260, 0x7801e0, 0x8701a0, 0x940180, + 0xa00160, 0xab0120, 0xb40120, 0xbd0120, 0xc60100, 0xce0100, + 0xd600e0, 0xdd00e0, 0xe400e0, 0xeb00c0, 0xf100c0, 0xf700c0, + 0xfd00c0, 0x10300a0, 0x10800c0, 0x10e00a0, 0x11300a0, 0x11800a0, + 0x11d00a0, 0x12200a0, 0x12700a0, 0x12c0080, 0x13000a0, 0x1350080, + 0x13900a0, 0x13e0080, 0x1420076, 0x17d0062, 0x1ae0054, 0x1d8004a, + 0x1fd0044, 0x21f003e, 0x23e003a, 0x25b0036, 0x2760032, 0x28f0030, + 0x2a7002e, 0x2be002c, 0x2d4002c, 0x2ea0028, 0x2fe0028, 0x3120026, + 0x3250024, 0x3370024, 0x3490022, 0x35a0022, 0x36b0020, 0x37b0020, + 0x38b0020, 0x39b001e, 0x3aa001e, 0x3b9001c, 0x3c7001c, 0x3d5001c, + 0x3e3001c, 0x3f1001c, 0x3ff001a, 0x40c001a }, +}; + +static int xisc_parse_dt(struct device *dev, struct isc_device *isc) +{ + struct device_node *np = dev->of_node; + struct device_node *epn = NULL; + struct isc_subdev_entity *subdev_entity; + unsigned int flags; + int ret; + bool mipi_mode; + + INIT_LIST_HEAD(&isc->subdev_entities); + + mipi_mode = of_property_read_bool(np, "microchip,mipi-mode"); + + while (1) { + struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; + + epn = of_graph_get_next_endpoint(np, epn); + if (!epn) + return 0; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), + &v4l2_epn); + if (ret) { + ret = -EINVAL; + dev_err(dev, "Could not parse the endpoint\n"); + break; + } + + subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), + GFP_KERNEL); + if (!subdev_entity) { + ret = -ENOMEM; + break; + } + subdev_entity->epn = epn; + + flags = v4l2_epn.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 = ISC_PFE_CFG0_HPOL_LOW; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_VPOL_LOW; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_PPOL_LOW; + + if (v4l2_epn.bus_type == V4L2_MBUS_BT656) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_CCIR_CRC | + ISC_PFE_CFG0_CCIR656; + + if (mipi_mode) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_MIPI; + + list_add_tail(&subdev_entity->list, &isc->subdev_entities); + } + of_node_put(epn); + + return ret; +} + +static int microchip_xisc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct isc_device *isc; + struct resource *res; + void __iomem *io_base; + struct isc_subdev_entity *subdev_entity; + int irq; + int ret; + u32 ver; + + isc = devm_kzalloc(dev, sizeof(*isc), GFP_KERNEL); + if (!isc) + return -ENOMEM; + + platform_set_drvdata(pdev, isc); + isc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + isc->regmap = devm_regmap_init_mmio(dev, io_base, µchip_isc_regmap_config); + if (IS_ERR(isc->regmap)) { + ret = PTR_ERR(isc->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, + "microchip-sama7g5-xisc", isc); + if (ret < 0) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + irq, ret); + return ret; + } + + isc->gamma_table = isc_sama7g5_gamma_table; + isc->gamma_max = 0; + + isc->max_width = ISC_SAMA7G5_MAX_SUPPORT_WIDTH; + isc->max_height = ISC_SAMA7G5_MAX_SUPPORT_HEIGHT; + + isc->config_dpc = isc_sama7g5_config_dpc; + isc->config_csc = isc_sama7g5_config_csc; + isc->config_cbc = isc_sama7g5_config_cbc; + isc->config_cc = isc_sama7g5_config_cc; + isc->config_gam = isc_sama7g5_config_gam; + isc->config_rlp = isc_sama7g5_config_rlp; + isc->config_ctrls = isc_sama7g5_config_ctrls; + + isc->adapt_pipeline = isc_sama7g5_adapt_pipeline; + + isc->offsets.csc = ISC_SAMA7G5_CSC_OFFSET; + isc->offsets.cbc = ISC_SAMA7G5_CBC_OFFSET; + isc->offsets.sub422 = ISC_SAMA7G5_SUB422_OFFSET; + isc->offsets.sub420 = ISC_SAMA7G5_SUB420_OFFSET; + isc->offsets.rlp = ISC_SAMA7G5_RLP_OFFSET; + isc->offsets.his = ISC_SAMA7G5_HIS_OFFSET; + isc->offsets.dma = ISC_SAMA7G5_DMA_OFFSET; + isc->offsets.version = ISC_SAMA7G5_VERSION_OFFSET; + isc->offsets.his_entry = ISC_SAMA7G5_HIS_ENTRY_OFFSET; + + isc->controller_formats = sama7g5_controller_formats; + isc->controller_formats_size = ARRAY_SIZE(sama7g5_controller_formats); + isc->formats_list = sama7g5_formats_list; + isc->formats_list_size = ARRAY_SIZE(sama7g5_formats_list); + + /* sama7g5-isc RAM access port is full AXI4 - 32 bits per beat */ + isc->dcfg = ISC_DCFG_YMBSIZE_BEATS32 | ISC_DCFG_CMBSIZE_BEATS32; + + /* sama7g5-isc : ISPCK does not exist, ISC is clocked by MCK */ + isc->ispck_required = false; + + ret = microchip_isc_pipeline_init(isc); + if (ret) + return ret; + + isc->hclock = devm_clk_get(dev, "hclock"); + if (IS_ERR(isc->hclock)) { + ret = PTR_ERR(isc->hclock); + dev_err(dev, "failed to get hclock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(isc->hclock); + if (ret) { + dev_err(dev, "failed to enable hclock: %d\n", ret); + return ret; + } + + ret = microchip_isc_clk_init(isc); + if (ret) { + dev_err(dev, "failed to init isc clock: %d\n", ret); + goto unprepare_hclk; + } + + ret = v4l2_device_register(dev, &isc->v4l2_dev); + if (ret) { + dev_err(dev, "unable to register v4l2 device.\n"); + goto unprepare_hclk; + } + + ret = xisc_parse_dt(dev, isc); + if (ret) { + dev_err(dev, "fail to parse device tree\n"); + goto unregister_v4l2_device; + } + + if (list_empty(&isc->subdev_entities)) { + dev_err(dev, "no subdev found\n"); + ret = -ENODEV; + goto unregister_v4l2_device; + } + + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode = + of_fwnode_handle(subdev_entity->epn); + + v4l2_async_nf_init(&subdev_entity->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&subdev_entity->notifier, + fwnode, + struct v4l2_async_subdev); + + of_node_put(subdev_entity->epn); + subdev_entity->epn = NULL; + + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto cleanup_subdev; + } + + subdev_entity->notifier.ops = µchip_isc_async_ops; + + ret = v4l2_async_nf_register(&isc->v4l2_dev, + &subdev_entity->notifier); + if (ret) { + dev_err(dev, "fail to register async notifier\n"); + goto cleanup_subdev; + } + + if (video_is_registered(&isc->video_dev)) + break; + } + + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); + + ret = isc_mc_init(isc, ver); + if (ret < 0) + goto isc_probe_mc_init_err; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_request_idle(dev); + + dev_info(dev, "Microchip XISC version %x\n", ver); + + return 0; + +isc_probe_mc_init_err: + isc_mc_cleanup(isc); + +cleanup_subdev: + microchip_isc_subdev_cleanup(isc); + +unregister_v4l2_device: + v4l2_device_unregister(&isc->v4l2_dev); + +unprepare_hclk: + clk_disable_unprepare(isc->hclock); + + microchip_isc_clk_cleanup(isc); + + return ret; +} + +static int microchip_xisc_remove(struct platform_device *pdev) +{ + struct isc_device *isc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + isc_mc_cleanup(isc); + + microchip_isc_subdev_cleanup(isc); + + v4l2_device_unregister(&isc->v4l2_dev); + + clk_disable_unprepare(isc->hclock); + + microchip_isc_clk_cleanup(isc); + + return 0; +} + +static int __maybe_unused xisc_runtime_suspend(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + + clk_disable_unprepare(isc->hclock); + + return 0; +} + +static int __maybe_unused xisc_runtime_resume(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(isc->hclock); + if (ret) + return ret; + + return ret; +} + +static const struct dev_pm_ops microchip_xisc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(xisc_runtime_suspend, xisc_runtime_resume, NULL) +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id microchip_xisc_of_match[] = { + { .compatible = "microchip,sama7g5-isc" }, + { } +}; +MODULE_DEVICE_TABLE(of, microchip_xisc_of_match); +#endif + +static struct platform_driver microchip_xisc_driver = { + .probe = microchip_xisc_probe, + .remove = microchip_xisc_remove, + .driver = { + .name = "microchip-sama7g5-xisc", + .pm = µchip_xisc_dev_pm_ops, + .of_match_table = of_match_ptr(microchip_xisc_of_match), + }, +}; + +module_platform_driver(microchip_xisc_driver); + +MODULE_AUTHOR("Eugen Hristev <eugen.hristev@microchip.com>"); +MODULE_DESCRIPTION("The V4L2 driver for Microchip-XISC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig index 5917634889b5..730f39971e1c 100644 --- a/drivers/media/platform/nxp/Kconfig +++ b/drivers/media/platform/nxp/Kconfig @@ -4,6 +4,19 @@ comment "NXP media platform drivers" +config VIDEO_IMX7_CSI + tristate "NXP CSI Bridge driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_DMA + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEOBUF2_DMA_CONTIG + select VIDEO_V4L2_SUBDEV_API + help + Driver for the NXP Camera Sensor Interface (CSI) Bridge. This device + is found in the i.MX6UL/L, i.MX7 and i.MX8M[MQ] SoCs. + config VIDEO_IMX_MIPI_CSIS tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile index 81ab304ef31c..1a129b58d99e 100644 --- a/drivers/media/platform/nxp/Makefile +++ b/drivers/media/platform/nxp/Makefile @@ -3,6 +3,7 @@ obj-y += dw100/ obj-y += imx-jpeg/ +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c index 9418fcf740a8..ef28122a5ed4 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c @@ -76,12 +76,14 @@ void print_wrapper_info(struct device *dev, void __iomem *reg) void mxc_jpeg_enable_irq(void __iomem *reg, int slot) { - writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + writel(0xF0C, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); } void mxc_jpeg_disable_irq(void __iomem *reg, int slot) { writel(0x0, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); } void mxc_jpeg_sw_reset(void __iomem *reg) diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index 32fd04a3d8bb..6cd015a35f7c 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -69,7 +69,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .fourcc = V4L2_PIX_FMT_JPEG, .subsampling = -1, .nc = -1, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .flags = MXC_JPEG_FMT_TYPE_ENC, }, { @@ -78,11 +79,13 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, .nc = 3, .depth = 24, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, .precision = 8, + .is_rgb = 1, }, { .name = "ABGR", /* ABGR packed format */ @@ -90,11 +93,13 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, .nc = 4, .depth = 32, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, .precision = 8, + .is_rgb = 1, }, { .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ @@ -102,7 +107,21 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, .nc = 3, .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ - .colplanes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .mem_planes = 2, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_NV12, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ + .mem_planes = 1, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ .h_align = 4, .v_align = 4, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -114,7 +133,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, .nc = 3, .depth = 16, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 4, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -126,7 +146,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, .nc = 3, .depth = 24, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -138,7 +159,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, .nc = 1, .depth = 8, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -330,6 +352,10 @@ static unsigned int debug; module_param(debug, int, 0644); MODULE_PARM_DESC(debug, "Debug level (0-3)"); +static unsigned int hw_timeout = 2000; +module_param(hw_timeout, int, 0644); +MODULE_PARM_DESC(hw_timeout, "MXC JPEG hw timeout, the number of milliseconds"); + static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision); static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q); @@ -415,6 +441,7 @@ static enum mxc_jpeg_image_format mxc_jpeg_fourcc_to_imgfmt(u32 fourcc) return MXC_JPEG_GRAY; case V4L2_PIX_FMT_YUYV: return MXC_JPEG_YUV422; + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: return MXC_JPEG_YUV420; case V4L2_PIX_FMT_YUV24: @@ -441,12 +468,17 @@ static void mxc_jpeg_addrs(struct mxc_jpeg_desc *desc, struct vb2_buffer *jpeg_buf, int offset) { int img_fmt = desc->stm_ctrl & STM_CTRL_IMAGE_FORMAT_MASK; + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(raw_buf->vb2_queue); + struct mxc_jpeg_q_data *q_data; + q_data = mxc_jpeg_get_q_data(ctx, raw_buf->type); desc->buf_base0 = vb2_dma_contig_plane_dma_addr(raw_buf, 0); desc->buf_base1 = 0; if (img_fmt == STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV420)) { - WARN_ON(raw_buf->num_planes < 2); - desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + if (raw_buf->num_planes == 2) + desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + else + desc->buf_base1 = desc->buf_base0 + q_data->sizeimage[0]; } desc->stm_bufbase = vb2_dma_contig_plane_dma_addr(jpeg_buf, 0) + offset; @@ -519,7 +551,6 @@ static bool mxc_jpeg_alloc_slot_data(struct mxc_jpeg_dev *jpeg, GFP_ATOMIC); if (!cfg_stm) goto err; - memset(cfg_stm, 0, MXC_JPEG_MAX_CFG_STREAM); jpeg->slot_data[slot].cfg_stream_vaddr = cfg_stm; skip_alloc: @@ -570,6 +601,48 @@ static void mxc_jpeg_check_and_set_last_buffer(struct mxc_jpeg_ctx *ctx, } } +static void mxc_jpeg_job_finish(struct mxc_jpeg_ctx *ctx, enum vb2_buffer_state state, bool reset) +{ + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, state); + v4l2_m2m_buf_done(dst_buf, state); + + mxc_jpeg_disable_irq(reg, ctx->slot); + ctx->mxc_jpeg->slot_data[ctx->slot].used = false; + if (reset) + mxc_jpeg_sw_reset(reg); +} + +static u32 mxc_jpeg_get_plane_size(struct mxc_jpeg_q_data *q_data, u32 plane_no) +{ + const struct mxc_jpeg_fmt *fmt = q_data->fmt; + u32 size; + int i; + + if (plane_no >= fmt->mem_planes) + return 0; + + if (fmt->mem_planes == fmt->comp_planes) + return q_data->sizeimage[plane_no]; + + if (plane_no < fmt->mem_planes - 1) + return q_data->sizeimage[plane_no]; + + size = q_data->sizeimage[fmt->mem_planes - 1]; + for (i = fmt->mem_planes; i < fmt->comp_planes; i++) + size += q_data->sizeimage[i]; + + return size; +} + static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) { struct mxc_jpeg_dev *jpeg = priv; @@ -602,6 +675,9 @@ static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) goto job_unlock; } + if (!jpeg->slot_data[slot].used) + goto job_unlock; + dec_ret = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); writel(dec_ret, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); /* w1c */ @@ -646,11 +722,11 @@ static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) payload); } else { q_data = mxc_jpeg_get_q_data(ctx, cap_type); - payload = q_data->sizeimage[0]; + payload = mxc_jpeg_get_plane_size(q_data, 0); vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); vb2_set_plane_payload(&dst_buf->vb2_buf, 1, 0); - if (q_data->fmt->colplanes == 2) { - payload = q_data->sizeimage[1]; + if (q_data->fmt->mem_planes == 2) { + payload = mxc_jpeg_get_plane_size(q_data, 1); vb2_set_plane_payload(&dst_buf->vb2_buf, 1, payload); } dev_dbg(dev, "Decoding finished, payload size: %ld + %ld\n", @@ -666,14 +742,9 @@ static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) buf_state = VB2_BUF_STATE_DONE; buffers_done: - mxc_jpeg_disable_irq(reg, ctx->slot); - jpeg->slot_data[slot].used = false; /* unused, but don't free */ - mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); - v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); - v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); - v4l2_m2m_buf_done(src_buf, buf_state); - v4l2_m2m_buf_done(dst_buf, buf_state); + mxc_jpeg_job_finish(ctx, buf_state, false); spin_unlock(&jpeg->hw_lock); + cancel_delayed_work(&ctx->task_timer); v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); return IRQ_HANDLED; job_unlock: @@ -694,6 +765,7 @@ static int mxc_jpeg_fixup_sof(struct mxc_jpeg_sof *sof, _bswap16(&sof->width); switch (fourcc) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: sof->components_no = 3; sof->comp[0].v = 0x2; @@ -730,6 +802,7 @@ static int mxc_jpeg_fixup_sos(struct mxc_jpeg_sos *sos, u8 *sof_u8 = (u8 *)sos; switch (fourcc) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: sos->components_no = 3; break; @@ -902,8 +975,8 @@ static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, jpeg->slot_data[slot].cfg_stream_size = mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, q_data->fmt->fourcc, - q_data->w, - q_data->h); + q_data->crop.width, + q_data->crop.height); /* chain the config descriptor with the encoding descriptor */ cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; @@ -920,11 +993,13 @@ static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, desc->next_descpt_ptr = 0; /* end of chain */ /* use adjusted resolution for CAST IP job */ - w = q_data->w_adjusted; - h = q_data->h_adjusted; + w = q_data->crop.width; + h = q_data->crop.height; + v4l_bound_align_image(&w, w, MXC_JPEG_MAX_WIDTH, q_data->fmt->h_align, + &h, h, MXC_JPEG_MAX_HEIGHT, q_data->fmt->v_align, 0); mxc_jpeg_set_res(desc, w, h); - mxc_jpeg_set_line_pitch(desc, w * (q_data->fmt->depth / 8)); - mxc_jpeg_set_bufsize(desc, desc->line_pitch * h); + mxc_jpeg_set_line_pitch(desc, q_data->bytesperline[0]); + mxc_jpeg_set_bufsize(desc, ALIGN(vb2_plane_size(dst_buf, 0), 1024)); img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data->fmt->fourcc); if (img_fmt == MXC_JPEG_INVALID) dev_err(jpeg->dev, "No valid image format detected\n"); @@ -943,6 +1018,32 @@ static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); } +static const struct mxc_jpeg_fmt *mxc_jpeg_get_sibling_format(const struct mxc_jpeg_fmt *fmt) +{ + int i; + + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) { + if (mxc_formats[i].subsampling == fmt->subsampling && + mxc_formats[i].nc == fmt->nc && + mxc_formats[i].precision == fmt->precision && + mxc_formats[i].is_rgb == fmt->is_rgb && + mxc_formats[i].fourcc != fmt->fourcc) + return &mxc_formats[i]; + } + + return NULL; +} + +static bool mxc_jpeg_compare_format(const struct mxc_jpeg_fmt *fmt1, + const struct mxc_jpeg_fmt *fmt2) +{ + if (fmt1 == fmt2) + return true; + if (mxc_jpeg_get_sibling_format(fmt1) == fmt2) + return true; + return false; +} + static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, struct mxc_jpeg_src_buf *jpeg_src_buf) { @@ -953,6 +1054,8 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, return false; q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (mxc_jpeg_compare_format(q_data_cap->fmt, jpeg_src_buf->fmt)) + jpeg_src_buf->fmt = q_data_cap->fmt; if (q_data_cap->fmt != jpeg_src_buf->fmt || q_data_cap->w != jpeg_src_buf->w || q_data_cap->h != jpeg_src_buf->h) { @@ -973,6 +1076,10 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, q_data_cap->fmt = jpeg_src_buf->fmt; q_data_cap->w_adjusted = q_data_cap->w; q_data_cap->h_adjusted = q_data_cap->h; + q_data_cap->crop.left = 0; + q_data_cap->crop.top = 0; + q_data_cap->crop.width = jpeg_src_buf->w; + q_data_cap->crop.height = jpeg_src_buf->h; /* * align up the resolution for CAST IP, @@ -985,7 +1092,7 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, &q_data_cap->h_adjusted, q_data_cap->h_adjusted, /* adjust up */ MXC_JPEG_MAX_HEIGHT, - 0, + q_data_cap->fmt->v_align, 0); /* setup bytesperline/sizeimage for capture queue */ @@ -994,6 +1101,7 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, notify_src_chg(ctx); ctx->source_change = 1; } + return ctx->source_change ? true : false; } @@ -1004,6 +1112,23 @@ static int mxc_jpeg_job_ready(void *priv) return ctx->source_change ? 0 : 1; } +static void mxc_jpeg_device_run_timeout(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct mxc_jpeg_ctx *ctx = container_of(dwork, struct mxc_jpeg_ctx, task_timer); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + unsigned long flags; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + if (ctx->slot < MXC_MAX_SLOTS && ctx->mxc_jpeg->slot_data[ctx->slot].used) { + dev_warn(jpeg->dev, "%s timeout, cancel it\n", + ctx->mxc_jpeg->mode == MXC_JPEG_DECODE ? "decode" : "encode"); + mxc_jpeg_job_finish(ctx, VB2_BUF_STATE_ERROR, true); + v4l2_m2m_job_finish(ctx->mxc_jpeg->m2m_dev, ctx->fh.m2m_ctx); + } + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); +} + static void mxc_jpeg_device_run(void *priv) { struct mxc_jpeg_ctx *ctx = priv; @@ -1035,9 +1160,9 @@ static void mxc_jpeg_device_run(void *priv) v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); - if (q_data_cap->fmt->colplanes != dst_buf->vb2_buf.num_planes) { + if (q_data_cap->fmt->mem_planes != dst_buf->vb2_buf.num_planes) { dev_err(dev, "Capture format %s has %d planes, but capture buffer has %d planes\n", - q_data_cap->fmt->name, q_data_cap->fmt->colplanes, + q_data_cap->fmt->name, q_data_cap->fmt->mem_planes, dst_buf->vb2_buf.num_planes); jpeg_src_buf->jpeg_parse_error = true; } @@ -1089,6 +1214,7 @@ static void mxc_jpeg_device_run(void *priv) &src_buf->vb2_buf, &dst_buf->vb2_buf); mxc_jpeg_dec_mode_go(dev, reg); } + schedule_delayed_work(&ctx->task_timer, msecs_to_jiffies(hw_timeout)); end: spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); } @@ -1098,6 +1224,7 @@ static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, { struct v4l2_fh *fh = file->private_data; struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + unsigned long flags; int ret; ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); @@ -1107,7 +1234,9 @@ static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, if (!vb2_is_streaming(v4l2_m2m_get_src_vq(fh->m2m_ctx))) return 0; + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); ret = v4l2_m2m_ioctl_decoder_cmd(file, priv, cmd); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); if (ret < 0) return ret; @@ -1128,6 +1257,7 @@ static int mxc_jpeg_encoder_cmd(struct file *file, void *priv, { struct v4l2_fh *fh = file->private_data; struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + unsigned long flags; int ret; ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, cmd); @@ -1138,7 +1268,9 @@ static int mxc_jpeg_encoder_cmd(struct file *file, void *priv, !vb2_is_streaming(v4l2_m2m_get_dst_vq(fh->m2m_ctx))) return 0; + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); ret = v4l2_m2m_ioctl_encoder_cmd(file, fh, cmd); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); if (ret < 0) return 0; @@ -1161,39 +1293,27 @@ static int mxc_jpeg_queue_setup(struct vb2_queue *q, { struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); struct mxc_jpeg_q_data *q_data = NULL; - struct mxc_jpeg_q_data tmp_q; int i; q_data = mxc_jpeg_get_q_data(ctx, q->type); if (!q_data) return -EINVAL; - tmp_q.fmt = q_data->fmt; - tmp_q.w = q_data->w_adjusted; - tmp_q.h = q_data->h_adjusted; - for (i = 0; i < MXC_JPEG_MAX_PLANES; i++) { - tmp_q.bytesperline[i] = q_data->bytesperline[i]; - tmp_q.sizeimage[i] = q_data->sizeimage[i]; - } - mxc_jpeg_sizeimage(&tmp_q); - for (i = 0; i < MXC_JPEG_MAX_PLANES; i++) - tmp_q.sizeimage[i] = max(tmp_q.sizeimage[i], q_data->sizeimage[i]); - /* Handle CREATE_BUFS situation - *nplanes != 0 */ if (*nplanes) { - if (*nplanes != q_data->fmt->colplanes) + if (*nplanes != q_data->fmt->mem_planes) return -EINVAL; for (i = 0; i < *nplanes; i++) { - if (sizes[i] < tmp_q.sizeimage[i]) + if (sizes[i] < mxc_jpeg_get_plane_size(q_data, i)) return -EINVAL; } return 0; } /* Handle REQBUFS situation */ - *nplanes = q_data->fmt->colplanes; + *nplanes = q_data->fmt->mem_planes; for (i = 0; i < *nplanes; i++) - sizes[i] = tmp_q.sizeimage[i]; + sizes[i] = mxc_jpeg_get_plane_size(q_data, i); return 0; } @@ -1238,7 +1358,8 @@ static void mxc_jpeg_stop_streaming(struct vb2_queue *q) v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); } - v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q); + if (V4L2_TYPE_IS_OUTPUT(q->type) || !ctx->source_change) + v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q); if (V4L2_TYPE_IS_OUTPUT(q->type) && v4l2_m2m_has_stopped(ctx->fh.m2m_ctx)) { notify_eos(ctx); @@ -1277,19 +1398,40 @@ static int mxc_jpeg_valid_comp_id(struct device *dev, return valid; } +static bool mxc_jpeg_match_image_format(const struct mxc_jpeg_fmt *fmt, + const struct v4l2_jpeg_header *header) +{ + if (fmt->subsampling != header->frame.subsampling || + fmt->nc != header->frame.num_components || + fmt->precision != header->frame.precision) + return false; + + /* + * If the transform flag from APP14 marker is 0, images that are + * encoded with 3 components have RGB colorspace, see Recommendation + * ITU-T T.872 chapter 6.5.3 APP14 marker segment for colour encoding + */ + if (header->frame.subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_444) { + u8 is_rgb = header->app14_tf == V4L2_JPEG_APP14_TF_CMYK_RGB ? 1 : 0; + + if (is_rgb != fmt->is_rgb) + return false; + } + return true; +} + static u32 mxc_jpeg_get_image_format(struct device *dev, const struct v4l2_jpeg_header *header) { int i; u32 fourcc = 0; - for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) - if (mxc_formats[i].subsampling == header->frame.subsampling && - mxc_formats[i].nc == header->frame.num_components && - mxc_formats[i].precision == header->frame.precision) { + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) { + if (mxc_jpeg_match_image_format(&mxc_formats[i], header)) { fourcc = mxc_formats[i].fourcc; break; } + } if (fourcc == 0) { dev_err(dev, "Could not identify image format nc=%d, subsampling=%d, precision=%d\n", @@ -1298,17 +1440,6 @@ static u32 mxc_jpeg_get_image_format(struct device *dev, header->frame.precision); return fourcc; } - /* - * If the transform flag from APP14 marker is 0, images that are - * encoded with 3 components have RGB colorspace, see Recommendation - * ITU-T T.872 chapter 6.5.3 APP14 marker segment for colour encoding - */ - if (fourcc == V4L2_PIX_FMT_YUV24 || fourcc == V4L2_PIX_FMT_BGR24) { - if (header->app14_tf == V4L2_JPEG_APP14_TF_CMYK_RGB) - fourcc = V4L2_PIX_FMT_BGR24; - else - fourcc = V4L2_PIX_FMT_YUV24; - } return fourcc; } @@ -1325,17 +1456,17 @@ static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision) * applies to the first plane and is divided by the same factor * as the width field for the other planes */ - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8); + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8); q->bytesperline[1] = q->bytesperline[0]; } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_422) { - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8) * 2; + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8) * 2; q->bytesperline[1] = 0; } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_444) { - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8) * q->fmt->nc; + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8) * q->fmt->nc; q->bytesperline[1] = 0; } else { /* grayscale */ - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8); + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8); q->bytesperline[1] = 0; } } @@ -1354,9 +1485,9 @@ static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q) /* jpeg stream size must be multiple of 1K */ q->sizeimage[0] = ALIGN(q->sizeimage[0], 1024); } else { - q->sizeimage[0] = q->bytesperline[0] * q->h; + q->sizeimage[0] = q->bytesperline[0] * q->h_adjusted; q->sizeimage[1] = 0; - if (q->fmt->fourcc == V4L2_PIX_FMT_NV12M) + if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_420) q->sizeimage[1] = q->sizeimage[0] / 2; } } @@ -1365,6 +1496,7 @@ static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, struct vb2_buffer *vb) { struct device *dev = ctx->mxc_jpeg->dev; struct mxc_jpeg_q_data *q_data_out; + struct mxc_jpeg_q_data *q_data_cap; u32 fourcc; struct v4l2_jpeg_header header; struct mxc_jpeg_sof *psof = NULL; @@ -1422,7 +1554,11 @@ static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, struct vb2_buffer *vb) if (!mxc_jpeg_valid_comp_id(dev, psof, psos)) dev_warn(dev, "JPEG component ids should be 0-3 or 1-4"); - fourcc = mxc_jpeg_get_image_format(dev, &header); + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (q_data_cap->fmt && mxc_jpeg_match_image_format(q_data_cap->fmt, &header)) + fourcc = q_data_cap->fmt->fourcc; + else + fourcc = mxc_jpeg_get_image_format(dev, &header); if (fourcc == 0) return -EINVAL; @@ -1498,8 +1634,8 @@ static int mxc_jpeg_buf_prepare(struct vb2_buffer *vb) q_data = mxc_jpeg_get_q_data(ctx, vb->vb2_queue->type); if (!q_data) return -EINVAL; - for (i = 0; i < q_data->fmt->colplanes; i++) { - sizeimage = q_data->sizeimage[i]; + for (i = 0; i < q_data->fmt->mem_planes; i++) { + sizeimage = mxc_jpeg_get_plane_size(q_data, i); if (vb2_plane_size(vb, i) < sizeimage) { dev_err(dev, "plane %d too small (%lu < %lu)", i, vb2_plane_size(vb, i), sizeimage); @@ -1578,6 +1714,10 @@ static void mxc_jpeg_set_default_params(struct mxc_jpeg_ctx *ctx) q[i]->h = MXC_JPEG_DEFAULT_HEIGHT; q[i]->w_adjusted = MXC_JPEG_DEFAULT_WIDTH; q[i]->h_adjusted = MXC_JPEG_DEFAULT_HEIGHT; + q[i]->crop.left = 0; + q[i]->crop.top = 0; + q[i]->crop.width = MXC_JPEG_DEFAULT_WIDTH; + q[i]->crop.height = MXC_JPEG_DEFAULT_HEIGHT; mxc_jpeg_bytesperline(q[i], q[i]->fmt->precision); mxc_jpeg_sizeimage(q[i]); } @@ -1672,6 +1812,7 @@ static int mxc_jpeg_open(struct file *file) ctx->fh.ctrl_handler = &ctx->ctrl_handler; mxc_jpeg_set_default_params(ctx); ctx->slot = MXC_MAX_SLOTS; /* slot not allocated yet */ + INIT_DELAYED_WORK(&ctx->task_timer, mxc_jpeg_device_run_timeout); if (mxc_jpeg->mode == MXC_JPEG_DECODE) dev_dbg(dev, "Opened JPEG decoder instance %p\n", ctx); @@ -1721,10 +1862,25 @@ static int mxc_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, * (more precisely what was propagated on capture queue * after jpeg parse on the output buffer) */ - if (f->index) - return -EINVAL; - f->pixelformat = q_data->fmt->fourcc; - return 0; + int ret = -EINVAL; + const struct mxc_jpeg_fmt *sibling; + + switch (f->index) { + case 0: + f->pixelformat = q_data->fmt->fourcc; + ret = 0; + break; + case 1: + sibling = mxc_jpeg_get_sibling_format(q_data->fmt); + if (sibling) { + f->pixelformat = sibling->fourcc; + ret = 0; + } + break; + default: + break; + } + return ret; } } @@ -1744,55 +1900,105 @@ static int mxc_jpeg_enum_fmt_vid_out(struct file *file, void *priv, return 0; } -static int mxc_jpeg_try_fmt(struct v4l2_format *f, const struct mxc_jpeg_fmt *fmt, - struct mxc_jpeg_ctx *ctx, int q_type) +static u32 mxc_jpeg_get_fmt_type(struct mxc_jpeg_ctx *ctx, u32 type) +{ + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return V4L2_TYPE_IS_OUTPUT(type) ? MXC_JPEG_FMT_TYPE_ENC : MXC_JPEG_FMT_TYPE_RAW; + else + return V4L2_TYPE_IS_CAPTURE(type) ? MXC_JPEG_FMT_TYPE_ENC : MXC_JPEG_FMT_TYPE_RAW; +} + +static u32 mxc_jpeg_get_default_fourcc(struct mxc_jpeg_ctx *ctx, u32 type) +{ + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return V4L2_TYPE_IS_OUTPUT(type) ? V4L2_PIX_FMT_JPEG : MXC_JPEG_DEFAULT_PFMT; + else + return V4L2_TYPE_IS_CAPTURE(type) ? V4L2_PIX_FMT_JPEG : MXC_JPEG_DEFAULT_PFMT; +} + +static u32 mxc_jpeg_try_fourcc(struct mxc_jpeg_ctx *ctx, u32 fourcc) +{ + const struct mxc_jpeg_fmt *sibling; + struct mxc_jpeg_q_data *q_data_cap; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + return fourcc; + if (!ctx->header_parsed) + return fourcc; + + q_data_cap = &ctx->cap_q; + if (q_data_cap->fmt->fourcc == fourcc) + return fourcc; + + sibling = mxc_jpeg_get_sibling_format(q_data_cap->fmt); + if (sibling && sibling->fourcc == fourcc) + return sibling->fourcc; + + return q_data_cap->fmt->fourcc; +} + +static int mxc_jpeg_try_fmt(struct v4l2_format *f, + struct mxc_jpeg_ctx *ctx, struct mxc_jpeg_q_data *q_data) { + const struct mxc_jpeg_fmt *fmt; struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct v4l2_plane_pix_format *pfmt; + u32 fourcc = f->fmt.pix_mp.pixelformat; u32 w = (pix_mp->width < MXC_JPEG_MAX_WIDTH) ? pix_mp->width : MXC_JPEG_MAX_WIDTH; u32 h = (pix_mp->height < MXC_JPEG_MAX_HEIGHT) ? pix_mp->height : MXC_JPEG_MAX_HEIGHT; int i; - struct mxc_jpeg_q_data tmp_q; + + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt || fmt->flags != mxc_jpeg_get_fmt_type(ctx, f->type)) { + dev_warn(ctx->mxc_jpeg->dev, "Format not supported: %c%c%c%c, use the default.\n", + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + fourcc = mxc_jpeg_get_default_fourcc(ctx, f->type); + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt) + return -EINVAL; + f->fmt.pix_mp.pixelformat = fourcc; + } + q_data->fmt = fmt; memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); pix_mp->field = V4L2_FIELD_NONE; - pix_mp->num_planes = fmt->colplanes; + pix_mp->num_planes = fmt->mem_planes; pix_mp->pixelformat = fmt->fourcc; - pix_mp->width = w; - pix_mp->height = h; - v4l_bound_align_image(&w, + q_data->w = w; + q_data->h = h; + q_data->w_adjusted = w; + q_data->h_adjusted = h; + v4l_bound_align_image(&q_data->w_adjusted, w, /* adjust upwards*/ MXC_JPEG_MAX_WIDTH, fmt->h_align, - &h, + &q_data->h_adjusted, h, /* adjust upwards*/ MXC_JPEG_MAX_HEIGHT, - 0, + fmt->v_align, 0); - - /* get user input into the tmp_q */ - tmp_q.w = w; - tmp_q.h = h; - tmp_q.fmt = fmt; for (i = 0; i < pix_mp->num_planes; i++) { pfmt = &pix_mp->plane_fmt[i]; - tmp_q.bytesperline[i] = pfmt->bytesperline; - tmp_q.sizeimage[i] = pfmt->sizeimage; + q_data->bytesperline[i] = pfmt->bytesperline; + q_data->sizeimage[i] = pfmt->sizeimage; } - /* calculate bytesperline & sizeimage into the tmp_q */ - mxc_jpeg_bytesperline(&tmp_q, fmt->precision); - mxc_jpeg_sizeimage(&tmp_q); + /* calculate bytesperline & sizeimage */ + mxc_jpeg_bytesperline(q_data, fmt->precision); + mxc_jpeg_sizeimage(q_data); /* adjust user format according to our calculations */ for (i = 0; i < pix_mp->num_planes; i++) { pfmt = &pix_mp->plane_fmt[i]; memset(pfmt->reserved, 0, sizeof(pfmt->reserved)); - pfmt->bytesperline = tmp_q.bytesperline[i]; - pfmt->sizeimage = tmp_q.sizeimage[i]; + pfmt->bytesperline = q_data->bytesperline[i]; + pfmt->sizeimage = mxc_jpeg_get_plane_size(q_data, i); } /* fix colorspace information to sRGB for both output & capture */ @@ -1806,6 +2012,16 @@ static int mxc_jpeg_try_fmt(struct v4l2_format *f, const struct mxc_jpeg_fmt *fm */ pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + if (fmt->flags == MXC_JPEG_FMT_TYPE_RAW) { + q_data->crop.left = 0; + q_data->crop.top = 0; + q_data->crop.width = q_data->w; + q_data->crop.height = q_data->h; + } + + pix_mp->width = q_data->w_adjusted; + pix_mp->height = q_data->h_adjusted; + return 0; } @@ -1815,29 +2031,17 @@ static int mxc_jpeg_try_fmt_vid_cap(struct file *file, void *priv, struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; struct device *dev = jpeg->dev; - const struct mxc_jpeg_fmt *fmt; - u32 fourcc = f->fmt.pix_mp.pixelformat; - - int q_type = (jpeg->mode == MXC_JPEG_DECODE) ? - MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + struct mxc_jpeg_q_data tmp_q; if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); return -EINVAL; } - fmt = mxc_jpeg_find_format(ctx, fourcc); - if (!fmt || fmt->flags != q_type) { - dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", - (fourcc & 0xff), - (fourcc >> 8) & 0xff, - (fourcc >> 16) & 0xff, - (fourcc >> 24) & 0xff); - f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_DECODE) ? - MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; - fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); - } - return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE && V4L2_TYPE_IS_CAPTURE(f->type)) + f->fmt.pix_mp.pixelformat = mxc_jpeg_try_fourcc(ctx, f->fmt.pix_mp.pixelformat); + + return mxc_jpeg_try_fmt(f, ctx, &tmp_q); } static int mxc_jpeg_try_fmt_vid_out(struct file *file, void *priv, @@ -1846,88 +2050,55 @@ static int mxc_jpeg_try_fmt_vid_out(struct file *file, void *priv, struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; struct device *dev = jpeg->dev; - const struct mxc_jpeg_fmt *fmt; - u32 fourcc = f->fmt.pix_mp.pixelformat; - - int q_type = (jpeg->mode == MXC_JPEG_ENCODE) ? - MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + struct mxc_jpeg_q_data tmp_q; if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); return -EINVAL; } - fmt = mxc_jpeg_find_format(ctx, fourcc); - if (!fmt || fmt->flags != q_type) { - dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", - (fourcc & 0xff), - (fourcc >> 8) & 0xff, - (fourcc >> 16) & 0xff, - (fourcc >> 24) & 0xff); - f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_ENCODE) ? - MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; - fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); - } - return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); + return mxc_jpeg_try_fmt(f, ctx, &tmp_q); +} + +static void mxc_jpeg_s_parsed_fmt(struct mxc_jpeg_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_q_data *q_data_cap; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE || !V4L2_TYPE_IS_CAPTURE(f->type)) + return; + if (!ctx->header_parsed) + return; + + q_data_cap = mxc_jpeg_get_q_data(ctx, f->type); + pix_mp->pixelformat = mxc_jpeg_try_fourcc(ctx, pix_mp->pixelformat); + pix_mp->width = q_data_cap->w; + pix_mp->height = q_data_cap->h; } static int mxc_jpeg_s_fmt(struct mxc_jpeg_ctx *ctx, struct v4l2_format *f) { struct vb2_queue *vq; - struct mxc_jpeg_q_data *q_data = NULL; - struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; - int i; vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; - q_data = mxc_jpeg_get_q_data(ctx, f->type); - if (vb2_is_busy(vq)) { v4l2_err(&jpeg->v4l2_dev, "queue busy\n"); return -EBUSY; } - q_data->fmt = mxc_jpeg_find_format(ctx, pix_mp->pixelformat); - q_data->w = pix_mp->width; - q_data->h = pix_mp->height; - - q_data->w_adjusted = q_data->w; - q_data->h_adjusted = q_data->h; - /* - * align up the resolution for CAST IP, - * but leave the buffer resolution unchanged - */ - v4l_bound_align_image(&q_data->w_adjusted, - q_data->w_adjusted, /* adjust upwards */ - MXC_JPEG_MAX_WIDTH, - q_data->fmt->h_align, - &q_data->h_adjusted, - q_data->h_adjusted, /* adjust upwards */ - MXC_JPEG_MAX_HEIGHT, - q_data->fmt->v_align, - 0); - - for (i = 0; i < pix_mp->num_planes; i++) { - q_data->bytesperline[i] = pix_mp->plane_fmt[i].bytesperline; - q_data->sizeimage[i] = pix_mp->plane_fmt[i].sizeimage; - } + mxc_jpeg_s_parsed_fmt(ctx, f); - return 0; + return mxc_jpeg_try_fmt(f, ctx, mxc_jpeg_get_q_data(ctx, f->type)); } static int mxc_jpeg_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - int ret; - - ret = mxc_jpeg_try_fmt_vid_cap(file, priv, f); - if (ret) - return ret; - return mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); } @@ -1941,10 +2112,6 @@ static int mxc_jpeg_s_fmt_vid_out(struct file *file, void *priv, enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; struct v4l2_format fc; - ret = mxc_jpeg_try_fmt_vid_out(file, priv, f); - if (ret) - return ret; - ret = mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); if (ret) return ret; @@ -1990,6 +2157,10 @@ static int mxc_jpeg_g_fmt_vid(struct file *file, void *priv, pix_mp->width = q_data->w; pix_mp->height = q_data->h; pix_mp->field = V4L2_FIELD_NONE; + if (q_data->fmt->flags == MXC_JPEG_FMT_TYPE_RAW) { + pix_mp->width = q_data->w_adjusted; + pix_mp->height = q_data->h_adjusted; + } /* fix colorspace information to sRGB for both output & capture */ pix_mp->colorspace = V4L2_COLORSPACE_SRGB; @@ -1997,15 +2168,109 @@ static int mxc_jpeg_g_fmt_vid(struct file *file, void *priv, pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; - pix_mp->num_planes = q_data->fmt->colplanes; + pix_mp->num_planes = q_data->fmt->mem_planes; for (i = 0; i < pix_mp->num_planes; i++) { pix_mp->plane_fmt[i].bytesperline = q_data->bytesperline[i]; - pix_mp->plane_fmt[i].sizeimage = q_data->sizeimage[i]; + pix_mp->plane_fmt[i].sizeimage = mxc_jpeg_get_plane_size(q_data, i); + } + + return 0; +} + +static int mxc_jpeg_dec_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_cap; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + q_data_cap = mxc_jpeg_get_q_data(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r = q_data_cap->crop; + break; + case V4L2_SEL_TGT_COMPOSE_PADDED: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data_cap->w_adjusted; + s->r.height = q_data_cap->h_adjusted; + break; + default: + return -EINVAL; } return 0; } +static int mxc_jpeg_enc_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_out; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + q_data_out = mxc_jpeg_get_q_data(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data_out->w; + s->r.height = q_data_out->h; + break; + case V4L2_SEL_TGT_CROP: + s->r = q_data_out->crop; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mxc_jpeg_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return mxc_jpeg_dec_g_selection(file, fh, s); + else + return mxc_jpeg_enc_g_selection(file, fh, s); +} + +static int mxc_jpeg_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_out; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_ENCODE) + return -ENOTTY; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + if (s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + q_data_out = mxc_jpeg_get_q_data(ctx, s->type); + if (s->r.left || s->r.top) + return -EINVAL; + if (s->r.width > q_data_out->w || s->r.height > q_data_out->h) + return -EINVAL; + + q_data_out->crop.left = 0; + q_data_out->crop.top = 0; + q_data_out->crop.width = s->r.width; + q_data_out->crop.height = s->r.height; + + return 0; +} + static int mxc_jpeg_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { @@ -2035,6 +2300,9 @@ static const struct v4l2_ioctl_ops mxc_jpeg_ioctl_ops = { .vidioc_g_fmt_vid_cap_mplane = mxc_jpeg_g_fmt_vid, .vidioc_g_fmt_vid_out_mplane = mxc_jpeg_g_fmt_vid, + .vidioc_g_selection = mxc_jpeg_g_selection, + .vidioc_s_selection = mxc_jpeg_s_selection, + .vidioc_subscribe_event = mxc_jpeg_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, @@ -2163,6 +2431,8 @@ static int mxc_jpeg_probe(struct platform_device *pdev) unsigned int slot; of_id = of_match_node(mxc_jpeg_match, dev->of_node); + if (!of_id) + return -ENODEV; mode = *(const int *)of_id->data; jpeg = devm_kzalloc(dev, sizeof(struct mxc_jpeg_dev), GFP_KERNEL); diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h index c508d41a906f..8fa8c0aec5a2 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -45,11 +45,13 @@ enum mxc_jpeg_mode { * @subsampling: subsampling of jpeg components * @nc: number of color components * @depth: number of bits per pixel - * @colplanes: number of color planes (1 for packed formats) + * @mem_planes: number of memory planes (1 for packed formats) + * @comp_planes:number of component planes, which includes the alpha plane (1 to 4). * @h_align: horizontal alignment order (align to 2^h_align) * @v_align: vertical alignment order (align to 2^v_align) * @flags: flags describing format applicability * @precision: jpeg sample precision + * @is_rgb: is an RGB pixel format */ struct mxc_jpeg_fmt { const char *name; @@ -57,11 +59,13 @@ struct mxc_jpeg_fmt { enum v4l2_jpeg_chroma_subsampling subsampling; int nc; int depth; - int colplanes; + int mem_planes; + int comp_planes; int h_align; int v_align; u32 flags; u8 precision; + u8 is_rgb; }; struct mxc_jpeg_desc { @@ -84,6 +88,7 @@ struct mxc_jpeg_q_data { int h; int h_adjusted; unsigned int sequence; + struct v4l2_rect crop; }; struct mxc_jpeg_ctx { @@ -97,6 +102,7 @@ struct mxc_jpeg_ctx { bool header_parsed; struct v4l2_ctrl_handler ctrl_handler; u8 jpeg_quality; + struct delayed_work task_timer; }; struct mxc_jpeg_slot_data { diff --git a/drivers/staging/media/imx/imx7-media-csi.c b/drivers/media/platform/nxp/imx7-media-csi.c index e5b550ccfa22..886374d3a6ff 100644 --- a/drivers/staging/media/imx/imx7-media-csi.c +++ b/drivers/media/platform/nxp/imx7-media-csi.c @@ -8,7 +8,6 @@ #include <linux/clk.h> #include <linux/delay.h> -#include <linux/gcd.h> #include <linux/interrupt.h> #include <linux/mfd/syscon.h> #include <linux/module.h> @@ -200,7 +199,7 @@ to_imx7_csi_vb2_buffer(struct vb2_buffer *vb) struct imx7_csi_dma_buf { void *virt; - dma_addr_t phys; + dma_addr_t dma_addr; unsigned long len; }; @@ -384,13 +383,13 @@ static void imx7_csi_dmareq_rff_disable(struct imx7_csi *csi) imx7_csi_reg_write(csi, cr3, CSI_CSICR3); } -static void imx7_csi_update_buf(struct imx7_csi *csi, dma_addr_t phys, +static void imx7_csi_update_buf(struct imx7_csi *csi, dma_addr_t dma_addr, int buf_num) { if (buf_num == 1) - imx7_csi_reg_write(csi, phys, CSI_CSIDMASA_FB2); + imx7_csi_reg_write(csi, dma_addr, CSI_CSIDMASA_FB2); else - imx7_csi_reg_write(csi, phys, CSI_CSIDMASA_FB1); + imx7_csi_reg_write(csi, dma_addr, CSI_CSIDMASA_FB1); } static struct imx7_csi_vb2_buffer *imx7_csi_video_next_buf(struct imx7_csi *csi); @@ -399,21 +398,22 @@ static void imx7_csi_setup_vb2_buf(struct imx7_csi *csi) { struct imx7_csi_vb2_buffer *buf; struct vb2_buffer *vb2_buf; - dma_addr_t phys[2]; int i; for (i = 0; i < 2; i++) { + dma_addr_t dma_addr; + buf = imx7_csi_video_next_buf(csi); if (buf) { csi->active_vb2_buf[i] = buf; vb2_buf = &buf->vbuf.vb2_buf; - phys[i] = vb2_dma_contig_plane_dma_addr(vb2_buf, 0); + dma_addr = vb2_dma_contig_plane_dma_addr(vb2_buf, 0); } else { csi->active_vb2_buf[i] = NULL; - phys[i] = csi->underrun_buf.phys; + dma_addr = csi->underrun_buf.dma_addr; } - imx7_csi_update_buf(csi, phys[i], i); + imx7_csi_update_buf(csi, dma_addr, i); } } @@ -440,10 +440,10 @@ static void imx7_csi_free_dma_buf(struct imx7_csi *csi, struct imx7_csi_dma_buf *buf) { if (buf->virt) - dma_free_coherent(csi->dev, buf->len, buf->virt, buf->phys); + dma_free_coherent(csi->dev, buf->len, buf->virt, buf->dma_addr); buf->virt = NULL; - buf->phys = 0; + buf->dma_addr = 0; } static int imx7_csi_alloc_dma_buf(struct imx7_csi *csi, @@ -452,7 +452,7 @@ static int imx7_csi_alloc_dma_buf(struct imx7_csi *csi, imx7_csi_free_dma_buf(csi, buf); buf->len = PAGE_ALIGN(size); - buf->virt = dma_alloc_coherent(csi->dev, buf->len, &buf->phys, + buf->virt = dma_alloc_coherent(csi->dev, buf->len, &buf->dma_addr, GFP_DMA | GFP_KERNEL); if (!buf->virt) return -ENOMEM; @@ -521,9 +521,9 @@ static void imx7_csi_configure(struct imx7_csi *csi) cr18 = imx7_csi_reg_read(csi, CSI_CSICR18); cr18 &= ~(BIT_CSI_HW_ENABLE | BIT_MIPI_DATA_FORMAT_MASK | - BIT_DATA_FROM_MIPI | BIT_BASEADDR_CHG_ERR_EN | - BIT_BASEADDR_SWITCH_EN | BIT_BASEADDR_SWITCH_SEL | - BIT_DEINTERLACE_EN); + BIT_DATA_FROM_MIPI | BIT_MIPI_DOUBLE_CMPNT | + BIT_BASEADDR_CHG_ERR_EN | BIT_BASEADDR_SWITCH_SEL | + BIT_BASEADDR_SWITCH_EN | BIT_DEINTERLACE_EN); if (out_pix->field == V4L2_FIELD_INTERLACED) { cr18 |= BIT_DEINTERLACE_EN; @@ -713,7 +713,7 @@ static void imx7_csi_vb2_buf_done(struct imx7_csi *csi) { struct imx7_csi_vb2_buffer *done, *next; struct vb2_buffer *vb; - dma_addr_t phys; + dma_addr_t dma_addr; done = csi->active_vb2_buf[csi->buf_num]; if (done) { @@ -728,14 +728,14 @@ static void imx7_csi_vb2_buf_done(struct imx7_csi *csi) /* get next queued buffer */ next = imx7_csi_video_next_buf(csi); if (next) { - phys = vb2_dma_contig_plane_dma_addr(&next->vbuf.vb2_buf, 0); + dma_addr = vb2_dma_contig_plane_dma_addr(&next->vbuf.vb2_buf, 0); csi->active_vb2_buf[csi->buf_num] = next; } else { - phys = csi->underrun_buf.phys; + dma_addr = csi->underrun_buf.dma_addr; csi->active_vb2_buf[csi->buf_num] = NULL; } - imx7_csi_update_buf(csi, phys, csi->buf_num); + imx7_csi_update_buf(csi, dma_addr, csi->buf_num); } static irqreturn_t imx7_csi_irq_handler(int irq, void *data) @@ -806,6 +806,30 @@ static irqreturn_t imx7_csi_irq_handler(int irq, void *data) * List of supported pixel formats for the subdevs. Keep V4L2_PIX_FMT_UYVY and * MEDIA_BUS_FMT_UYVY8_2X8 first to match IMX7_CSI_DEF_PIX_FORMAT and * IMX7_CSI_DEF_MBUS_CODE. + * + * TODO: Restrict the supported formats list based on the SoC integration. + * + * The CSI bridge can be configured to sample pixel components from the Rx queue + * in single (8bpp) or double (16bpp) component modes. Image format variants + * with different sample sizes (ie YUYV_2X8 vs YUYV_1X16) determine the pixel + * components sampling size per each clock cycle and their packing mode (see + * imx7_csi_configure() for details). + * + * As the CSI bridge can be interfaced with different IP blocks depending on the + * SoC model it is integrated on, the Rx queue sampling size should match the + * size of the samples transferred by the transmitting IP block. To avoid + * misconfigurations of the capture pipeline, the enumeration of the supported + * formats should be restricted to match the pixel source transmitting mode. + * + * Example: i.MX8MM SoC integrates the CSI bridge with the Samsung CSIS CSI-2 + * receiver which operates in dual pixel sampling mode. The CSI bridge should + * only expose the 1X16 formats variant which instructs it to operate in dual + * pixel sampling mode. When the CSI bridge is instead integrated on an i.MX7, + * which supports both serial and parallel input, it should expose both + * variants. + * + * This currently only applies to YUYV formats, but other formats might need to + * be handled in the same way. */ static const struct imx7_csi_pixfmt pixel_formats[] = { /*** YUV formats start here ***/ @@ -1296,12 +1320,88 @@ static int imx7_csi_video_buf_prepare(struct vb2_buffer *vb) return 0; } +static bool imx7_csi_fast_track_buffer(struct imx7_csi *csi, + struct imx7_csi_vb2_buffer *buf) +{ + unsigned long flags; + dma_addr_t dma_addr; + int buf_num; + u32 isr; + + if (!csi->is_streaming) + return false; + + dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vbuf.vb2_buf, 0); + + /* + * buf_num holds the framebuffer ID of the most recently (*not* the + * next anticipated) triggered interrupt. Without loss of generality, + * if buf_num is 0, the hardware is capturing to FB2. If FB1 has been + * programmed with a dummy buffer (as indicated by active_vb2_buf[0] + * being NULL), then we can fast-track the new buffer by programming + * its address in FB1 before the hardware completes FB2, instead of + * adding it to the buffer queue and incurring a delay of one + * additional frame. + * + * The irqlock prevents races with the interrupt handler that updates + * buf_num when it programs the next buffer, but we can still race with + * the hardware if we program the buffer in FB1 just after the hardware + * completes FB2 and switches to FB1 and before buf_num can be updated + * by the interrupt handler for FB2. The fast-tracked buffer would + * then be ignored by the hardware while the driver would think it has + * successfully been processed. + * + * To avoid this problem, if we can't avoid the race, we can detect + * that we have lost it by checking, after programming the buffer in + * FB1, if the interrupt flag indicating completion of FB2 has been + * raised. If that is not the case, fast-tracking succeeded, and we can + * update active_vb2_buf[0]. Otherwise, we may or may not have lost the + * race (as the interrupt flag may have been raised just after + * programming FB1 and before we read the interrupt status register), + * and we need to assume the worst case of a race loss and queue the + * buffer through the slow path. + */ + + spin_lock_irqsave(&csi->irqlock, flags); + + buf_num = csi->buf_num; + if (csi->active_vb2_buf[buf_num]) { + spin_unlock_irqrestore(&csi->irqlock, flags); + return false; + } + + imx7_csi_update_buf(csi, dma_addr, buf_num); + + isr = imx7_csi_reg_read(csi, CSI_CSISR); + if (isr & (buf_num ? BIT_DMA_TSF_DONE_FB1 : BIT_DMA_TSF_DONE_FB2)) { + /* + * The interrupt for the /other/ FB just came (the isr hasn't + * run yet though, because we have the lock here); we can't be + * sure we've programmed buf_num FB in time, so queue the buffer + * to the buffer queue normally. No need to undo writing the FB + * register, since we won't return it as active_vb2_buf is NULL, + * so it's okay to potentially write it to both FB1 and FB2; + * only the one where it was queued normally will be returned. + */ + spin_unlock_irqrestore(&csi->irqlock, flags); + return false; + } + + csi->active_vb2_buf[buf_num] = buf; + + spin_unlock_irqrestore(&csi->irqlock, flags); + return true; +} + static void imx7_csi_video_buf_queue(struct vb2_buffer *vb) { struct imx7_csi *csi = vb2_get_drv_priv(vb->vb2_queue); struct imx7_csi_vb2_buffer *buf = to_imx7_csi_vb2_buffer(vb); unsigned long flags; + if (imx7_csi_fast_track_buffer(csi, buf)) + return; + spin_lock_irqsave(&csi->q_lock, flags); list_add_tail(&buf->list, &csi->ready_q); diff --git a/drivers/media/platform/qcom/camss/camss-vfe-170.c b/drivers/media/platform/qcom/camss/camss-vfe-170.c index 600150cfc4f7..8e506a805d11 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe-170.c +++ b/drivers/media/platform/qcom/camss/camss-vfe-170.c @@ -687,7 +687,12 @@ out_unlock: */ static void vfe_pm_domain_off(struct vfe_device *vfe) { - /* nop */ + struct camss *camss = vfe->camss; + + if (vfe->id >= camss->vfe_num) + return; + + device_link_del(camss->genpd_link[vfe->id]); } /* @@ -696,6 +701,19 @@ static void vfe_pm_domain_off(struct vfe_device *vfe) */ static int vfe_pm_domain_on(struct vfe_device *vfe) { + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + if (id >= camss->vfe_num) + return 0; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[id]) + return -EINVAL; + return 0; } diff --git a/drivers/media/platform/qcom/camss/camss-vfe-480.c b/drivers/media/platform/qcom/camss/camss-vfe-480.c index 129585110393..3aa962b5663b 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe-480.c +++ b/drivers/media/platform/qcom/camss/camss-vfe-480.c @@ -494,7 +494,12 @@ out_unlock: */ static void vfe_pm_domain_off(struct vfe_device *vfe) { - /* nop */ + struct camss *camss = vfe->camss; + + if (vfe->id >= camss->vfe_num) + return; + + device_link_del(camss->genpd_link[vfe->id]); } /* @@ -503,6 +508,19 @@ static void vfe_pm_domain_off(struct vfe_device *vfe) */ static int vfe_pm_domain_on(struct vfe_device *vfe) { + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + if (id >= camss->vfe_num) + return 0; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[id]) + return -EINVAL; + return 0; } diff --git a/drivers/media/platform/qcom/camss/camss-video.c b/drivers/media/platform/qcom/camss/camss-video.c index 81fb3a5bc1d5..41deda232e4a 100644 --- a/drivers/media/platform/qcom/camss/camss-video.c +++ b/drivers/media/platform/qcom/camss/camss-video.c @@ -495,7 +495,7 @@ static int video_start_streaming(struct vb2_queue *q, unsigned int count) ret = video_device_pipeline_start(vdev, &video->pipe); if (ret < 0) - return ret; + goto flush_buffers; ret = video_check_format(video); if (ret < 0) @@ -524,6 +524,7 @@ static int video_start_streaming(struct vb2_queue *q, unsigned int count) error: video_device_pipeline_stop(vdev); +flush_buffers: video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED); return ret; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 1118c40886d5..9cda284f1e71 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -1170,7 +1170,7 @@ static int camss_init_subdevices(struct camss *camss) } /* note: SM8250 requires VFE to be initialized before CSID */ - for (i = 0; i < camss->vfe_num; i++) { + for (i = 0; i < camss->vfe_num + camss->vfe_lite_num; i++) { ret = msm_vfe_subdev_init(camss, &camss->vfe[i], &vfe_res[i], i); if (ret < 0) { @@ -1242,7 +1242,7 @@ static int camss_register_entities(struct camss *camss) goto err_reg_ispif; } - for (i = 0; i < camss->vfe_num; i++) { + for (i = 0; i < camss->vfe_num + camss->vfe_lite_num; i++) { ret = msm_vfe_register_entities(&camss->vfe[i], &camss->v4l2_dev); if (ret < 0) { @@ -1314,7 +1314,7 @@ static int camss_register_entities(struct camss *camss) } } else { for (i = 0; i < camss->csid_num; i++) - for (k = 0; k < camss->vfe_num; k++) + for (k = 0; k < camss->vfe_num + camss->vfe_lite_num; k++) for (j = 0; j < camss->vfe[k].line_num; j++) { struct v4l2_subdev *csid = &camss->csid[i].subdev; struct v4l2_subdev *vfe = &camss->vfe[k].line[j].subdev; @@ -1338,7 +1338,7 @@ static int camss_register_entities(struct camss *camss) return 0; err_link: - i = camss->vfe_num; + i = camss->vfe_num + camss->vfe_lite_num; err_reg_vfe: for (i--; i >= 0; i--) msm_vfe_unregister_entities(&camss->vfe[i]); @@ -1377,7 +1377,7 @@ static void camss_unregister_entities(struct camss *camss) msm_ispif_unregister_entities(camss->ispif); - for (i = 0; i < camss->vfe_num; i++) + for (i = 0; i < camss->vfe_num + camss->vfe_lite_num; i++) msm_vfe_unregister_entities(&camss->vfe[i]); } @@ -1453,7 +1453,6 @@ static const struct media_device_ops camss_media_ops = { static int camss_configure_pd(struct camss *camss) { struct device *dev = camss->dev; - int last_pm_domain = 0; int i; int ret; @@ -1465,6 +1464,14 @@ static int camss_configure_pd(struct camss *camss) return camss->genpd_num; } + /* + * If a platform device has just one power domain, then it is attached + * at platform_probe() level, thus there shall be no need and even no + * option to attach it again, this is the case for CAMSS on MSM8916. + */ + if (camss->genpd_num == 1) + return 0; + camss->genpd = devm_kmalloc_array(dev, camss->genpd_num, sizeof(*camss->genpd), GFP_KERNEL); if (!camss->genpd) @@ -1476,32 +1483,34 @@ static int camss_configure_pd(struct camss *camss) if (!camss->genpd_link) return -ENOMEM; + /* + * VFE power domains are in the beginning of the list, and while all + * power domains should be attached, only if TITAN_TOP power domain is + * found in the list, it should be linked over here. + */ for (i = 0; i < camss->genpd_num; i++) { camss->genpd[i] = dev_pm_domain_attach_by_id(camss->dev, i); if (IS_ERR(camss->genpd[i])) { ret = PTR_ERR(camss->genpd[i]); goto fail_pm; } + } - camss->genpd_link[i] = device_link_add(camss->dev, camss->genpd[i], - DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | - DL_FLAG_RPM_ACTIVE); - if (!camss->genpd_link[i]) { - dev_pm_domain_detach(camss->genpd[i], true); + if (i > camss->vfe_num) { + camss->genpd_link[i - 1] = device_link_add(camss->dev, camss->genpd[i - 1], + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[i - 1]) { ret = -EINVAL; goto fail_pm; } - - last_pm_domain = i; } return 0; fail_pm: - for (i = 0; i < last_pm_domain; i++) { - device_link_del(camss->genpd_link[i]); + for (--i ; i >= 0; i--) dev_pm_domain_detach(camss->genpd[i], true); - } return ret; } @@ -1571,13 +1580,15 @@ static int camss_probe(struct platform_device *pdev) camss->version = CAMSS_845; camss->csiphy_num = 4; camss->csid_num = 3; - camss->vfe_num = 3; + camss->vfe_num = 2; + camss->vfe_lite_num = 1; } else if (of_device_is_compatible(dev->of_node, "qcom,sm8250-camss")) { camss->version = CAMSS_8250; camss->csiphy_num = 6; camss->csid_num = 4; - camss->vfe_num = 4; + camss->vfe_num = 2; + camss->vfe_lite_num = 2; } else { return -EINVAL; } @@ -1599,8 +1610,8 @@ static int camss_probe(struct platform_device *pdev) return -ENOMEM; } - camss->vfe = devm_kcalloc(dev, camss->vfe_num, sizeof(*camss->vfe), - GFP_KERNEL); + camss->vfe = devm_kcalloc(dev, camss->vfe_num + camss->vfe_lite_num, + sizeof(*camss->vfe), GFP_KERNEL); if (!camss->vfe) return -ENOMEM; @@ -1698,10 +1709,14 @@ void camss_delete(struct camss *camss) pm_runtime_disable(camss->dev); - for (i = 0; i < camss->genpd_num; i++) { - device_link_del(camss->genpd_link[i]); + if (camss->genpd_num == 1) + return; + + if (camss->genpd_num > camss->vfe_num) + device_link_del(camss->genpd_link[camss->genpd_num - 1]); + + for (i = 0; i < camss->genpd_num; i++) dev_pm_domain_detach(camss->genpd[i], true); - } } /* diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index 0db80cadbbaa..3acd2b3403e8 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -97,6 +97,7 @@ struct camss { struct csid_device *csid; struct ispif_device *ispif; int vfe_num; + int vfe_lite_num; struct vfe_device *vfe; atomic_t ref_count; int genpd_num; diff --git a/drivers/media/platform/qcom/venus/firmware.c b/drivers/media/platform/qcom/venus/firmware.c index 14b6f1d05991..142d4c74017c 100644 --- a/drivers/media/platform/qcom/venus/firmware.c +++ b/drivers/media/platform/qcom/venus/firmware.c @@ -38,8 +38,8 @@ static void venus_reset_cpu(struct venus_core *core) writel(fw_size, wrapper_base + WRAPPER_FW_END_ADDR); writel(0, wrapper_base + WRAPPER_CPA_START_ADDR); writel(fw_size, wrapper_base + WRAPPER_CPA_END_ADDR); - writel(fw_size, wrapper_base + WRAPPER_NONPIX_START_ADDR); - writel(fw_size, wrapper_base + WRAPPER_NONPIX_END_ADDR); + writel(0, wrapper_base + WRAPPER_NONPIX_START_ADDR); + writel(0, wrapper_base + WRAPPER_NONPIX_END_ADDR); if (IS_V6(core)) { /* Bring XTSS out of reset */ @@ -68,9 +68,11 @@ int venus_set_hw_state(struct venus_core *core, bool resume) venus_reset_cpu(core); } else { if (IS_V6(core)) - writel(1, core->wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + writel(WRAPPER_XTSS_SW_RESET_BIT, + core->wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); else - writel(1, core->wrapper_base + WRAPPER_A9SS_SW_RESET); + writel(WRAPPER_A9SS_SW_RESET_BIT, + core->wrapper_base + WRAPPER_A9SS_SW_RESET); } return 0; @@ -179,17 +181,15 @@ static int venus_shutdown_no_tz(struct venus_core *core) if (IS_V6(core)) { /* Assert the reset to XTSS */ - reg = readl_relaxed(wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + reg = readl(wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); reg |= WRAPPER_XTSS_SW_RESET_BIT; - writel_relaxed(reg, wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + writel(reg, wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); } else { /* Assert the reset to ARM9 */ - reg = readl_relaxed(wrapper_base + WRAPPER_A9SS_SW_RESET); + reg = readl(wrapper_base + WRAPPER_A9SS_SW_RESET); reg |= WRAPPER_A9SS_SW_RESET_BIT; - writel_relaxed(reg, wrapper_base + WRAPPER_A9SS_SW_RESET); + writel(reg, wrapper_base + WRAPPER_A9SS_SW_RESET); } - /* Make sure reset is asserted before the mapping is removed */ - mb(); iommu = core->fw.iommu_domain; diff --git a/drivers/media/platform/qcom/venus/pm_helpers.c b/drivers/media/platform/qcom/venus/pm_helpers.c index c93d2906e4c7..48c9084bb4db 100644 --- a/drivers/media/platform/qcom/venus/pm_helpers.c +++ b/drivers/media/platform/qcom/venus/pm_helpers.c @@ -869,8 +869,8 @@ static int vcodec_domains_get(struct venus_core *core) for (i = 0; i < res->vcodec_pmdomains_num; i++) { pd = dev_pm_domain_attach_by_name(dev, res->vcodec_pmdomains[i]); - if (IS_ERR(pd)) - return PTR_ERR(pd); + if (IS_ERR_OR_NULL(pd)) + return PTR_ERR(pd) ? : -ENODATA; core->pmdomains[i] = pd; } diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig index 9fd90672ea2d..ed788e991f74 100644 --- a/drivers/media/platform/renesas/Kconfig +++ b/drivers/media/platform/renesas/Kconfig @@ -41,6 +41,7 @@ config VIDEO_SH_VOU Support for the Video Output Unit (VOU) on SuperH SoCs. source "drivers/media/platform/renesas/rcar-vin/Kconfig" +source "drivers/media/platform/renesas/rzg2l-cru/Kconfig" # Mem2mem drivers diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile index 3ec226ef5fd2..55854e868887 100644 --- a/drivers/media/platform/renesas/Makefile +++ b/drivers/media/platform/renesas/Makefile @@ -4,6 +4,7 @@ # obj-y += rcar-vin/ +obj-y += rzg2l-cru/ obj-y += vsp1/ obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 2f7daa853ed8..5e53d6b7036c 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -1131,6 +1131,7 @@ static const struct rvin_info rcar_info_h1 = { .use_mc = false, .max_width = 2048, .max_height = 2048, + .scaler = rvin_scaler_gen2, }; static const struct rvin_info rcar_info_m1 = { @@ -1138,6 +1139,7 @@ static const struct rvin_info rcar_info_m1 = { .use_mc = false, .max_width = 2048, .max_height = 2048, + .scaler = rvin_scaler_gen2, }; static const struct rvin_info rcar_info_gen2 = { @@ -1145,6 +1147,7 @@ static const struct rvin_info rcar_info_gen2 = { .use_mc = false, .max_width = 2048, .max_height = 2048, + .scaler = rvin_scaler_gen2, }; static const struct rvin_group_route rcar_info_r8a774e1_routes[] = { @@ -1177,6 +1180,7 @@ static const struct rvin_info rcar_info_r8a7795 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a7795_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a7795es1_routes[] = { @@ -1212,6 +1216,7 @@ static const struct rvin_info rcar_info_r8a7796 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a7796_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a77965_routes[] = { @@ -1229,6 +1234,7 @@ static const struct rvin_info rcar_info_r8a77965 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a77965_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a77970_routes[] = { @@ -1271,6 +1277,7 @@ static const struct rvin_info rcar_info_r8a77990 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a77990_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a77995_routes[] = { @@ -1284,6 +1291,7 @@ static const struct rvin_info rcar_info_r8a77995 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a77995_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_info rcar_info_r8a779a0 = { @@ -1408,13 +1416,21 @@ static int rcar_vin_probe(struct platform_device *pdev) platform_set_drvdata(pdev, vin); - if (vin->info->use_isp) + if (vin->info->use_isp) { ret = rvin_isp_init(vin); - else if (vin->info->use_mc) + } else if (vin->info->use_mc) { ret = rvin_csi2_init(vin); - else + + if (vin->info->scaler && + rvin_group_id_to_master(vin->id) == vin->id) + vin->scaler = vin->info->scaler; + } else { ret = rvin_parallel_init(vin); + if (vin->info->scaler) + vin->scaler = vin->info->scaler; + } + if (ret) { rvin_dma_unregister(vin); return ret; diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 3aea96d85165..98bfd445a649 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -74,6 +74,10 @@ /* Register offsets specific for Gen3 */ #define VNCSI_IFMD_REG 0x20 /* Video n CSI2 Interface Mode Register */ +#define VNUDS_CTRL_REG 0x80 /* Video n scaling control register */ +#define VNUDS_SCALE_REG 0x84 /* Video n scaling factor register */ +#define VNUDS_PASS_BWIDTH_REG 0x90 /* Video n passband register */ +#define VNUDS_CLIP_SIZE_REG 0xa4 /* Video n UDS output size clipping reg */ /* Register bit fields for R-Car VIN */ /* Video n Main Control Register bits */ @@ -140,6 +144,9 @@ #define VNCSI_IFMD_DES0 (1 << 25) #define VNCSI_IFMD_CSI_CHSEL(n) (((n) & 0xf) << 0) +/* Video n scaling control register (Gen3) */ +#define VNUDS_CTRL_AMD (1 << 30) + struct rvin_buffer { struct vb2_v4l2_buffer vb; struct list_head list; @@ -160,9 +167,17 @@ static u32 rvin_read(struct rvin_dev *vin, u32 offset) } /* ----------------------------------------------------------------------------- - * Crop and Scaling Gen2 + * Crop and Scaling */ +static bool rvin_scaler_needed(const struct rvin_dev *vin) +{ + return !(vin->crop.width == vin->format.width && + vin->compose.width == vin->format.width && + vin->crop.height == vin->format.height && + vin->compose.height == vin->format.height); +} + struct vin_coeff { unsigned short xs_value; u32 coeff_set[24]; @@ -535,7 +550,7 @@ static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs) rvin_write(vin, p_set->coeff_set[23], VNC8C_REG); } -static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin) +void rvin_scaler_gen2(struct rvin_dev *vin) { unsigned int crop_height; u32 xs, ys; @@ -583,6 +598,69 @@ static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin) 0, 0); } +static unsigned int rvin_uds_scale_ratio(unsigned int in, unsigned int out) +{ + unsigned int ratio; + + ratio = in * 4096 / out; + return ratio >= 0x10000 ? 0xffff : ratio; +} + +static unsigned int rvin_uds_filter_width(unsigned int ratio) +{ + if (ratio >= 0x1000) + return 64 * (ratio & 0xf000) / ratio; + + return 64; +} + +void rvin_scaler_gen3(struct rvin_dev *vin) +{ + unsigned int ratio_h, ratio_v; + unsigned int bwidth_h, bwidth_v; + u32 vnmc, clip_size; + + vnmc = rvin_read(vin, VNMC_REG); + + /* Disable scaler if not needed. */ + if (!rvin_scaler_needed(vin)) { + rvin_write(vin, vnmc & ~VNMC_SCLE, VNMC_REG); + return; + } + + ratio_h = rvin_uds_scale_ratio(vin->crop.width, vin->compose.width); + bwidth_h = rvin_uds_filter_width(ratio_h); + + ratio_v = rvin_uds_scale_ratio(vin->crop.height, vin->compose.height); + bwidth_v = rvin_uds_filter_width(ratio_v); + + clip_size = vin->compose.width << 16; + + switch (vin->format.field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + clip_size |= vin->compose.height / 2; + break; + default: + clip_size |= vin->compose.height; + break; + } + + rvin_write(vin, vnmc | VNMC_SCLE, VNMC_REG); + rvin_write(vin, VNUDS_CTRL_AMD, VNUDS_CTRL_REG); + rvin_write(vin, (ratio_h << 16) | ratio_v, VNUDS_SCALE_REG); + rvin_write(vin, (bwidth_h << 16) | bwidth_v, VNUDS_PASS_BWIDTH_REG); + rvin_write(vin, clip_size, VNUDS_CLIP_SIZE_REG); + + vin_dbg(vin, "Pre-Clip: %ux%u@%u:%u Post-Clip: %ux%u@%u:%u\n", + vin->crop.width, vin->crop.height, vin->crop.left, + vin->crop.top, vin->compose.width, vin->compose.height, + vin->compose.left, vin->compose.top); +} + void rvin_crop_scale_comp(struct rvin_dev *vin) { const struct rvin_video_format *fmt; @@ -594,9 +672,8 @@ void rvin_crop_scale_comp(struct rvin_dev *vin) rvin_write(vin, vin->crop.top, VNSLPRC_REG); rvin_write(vin, vin->crop.top + vin->crop.height - 1, VNELPRC_REG); - /* TODO: Add support for the UDS scaler. */ - if (vin->info->model != RCAR_GEN3) - rvin_crop_scale_comp_gen2(vin); + if (vin->scaler) + vin->scaler(vin); fmt = rvin_format_from_pixel(vin, vin->format.pixelformat); stride = vin->format.bytesperline / fmt->bpp; @@ -984,12 +1061,12 @@ static int rvin_capture_start(struct rvin_dev *vin) for (slot = 0; slot < HW_BUFFER_NUM; slot++) rvin_fill_hw_slot(vin, slot); - rvin_crop_scale_comp(vin); - ret = rvin_setup(vin); if (ret) return ret; + rvin_crop_scale_comp(vin); + vin_dbg(vin, "Starting to capture\n"); /* Continuous Frame Capture Mode */ @@ -1234,9 +1311,16 @@ static int rvin_mc_validate_format(struct rvin_dev *vin, struct v4l2_subdev *sd, return -EPIPE; } - if (fmt.format.width != vin->format.width || - fmt.format.height != vin->format.height || - fmt.format.code != vin->mbus_code) + if (rvin_scaler_needed(vin)) { + if (!vin->scaler) + return -EPIPE; + } else { + if (fmt.format.width != vin->format.width || + fmt.format.height != vin->format.height) + return -EPIPE; + } + + if (fmt.format.code != vin->mbus_code) return -EPIPE; return 0; diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c index 576059f9bbe3..073f70c6ac68 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c @@ -226,10 +226,10 @@ static int rvin_reset_format(struct rvin_dev *vin) v4l2_fill_pix_format(&vin->format, &fmt.format); - vin->src_rect.top = 0; - vin->src_rect.left = 0; - vin->src_rect.width = vin->format.width; - vin->src_rect.height = vin->format.height; + vin->crop.top = 0; + vin->crop.left = 0; + vin->crop.width = vin->format.width; + vin->crop.height = vin->format.height; /* Make use of the hardware interlacer by default. */ if (vin->format.field == V4L2_FIELD_ALTERNATE) { @@ -239,8 +239,6 @@ static int rvin_reset_format(struct rvin_dev *vin) rvin_format_align(vin, &vin->format); - vin->crop = vin->src_rect; - vin->compose.top = 0; vin->compose.left = 0; vin->compose.width = vin->format.width; @@ -349,7 +347,6 @@ static int rvin_s_fmt_vid_cap(struct file *file, void *priv, v4l2_rect_map_inside(&vin->crop, &src_rect); v4l2_rect_map_inside(&vin->compose, &fmt_rect); - vin->src_rect = src_rect; return 0; } @@ -428,10 +425,60 @@ static int rvin_enum_fmt_vid_cap(struct file *file, void *priv, return -EINVAL; } +static int rvin_remote_rectangle(struct rvin_dev *vin, struct v4l2_rect *rect) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_subdev *sd; + unsigned int index; + int ret; + + if (vin->info->use_mc) { + struct media_pad *pad = media_pad_remote_pad_first(&vin->pad); + + if (!pad) + return -EINVAL; + + sd = media_entity_to_v4l2_subdev(pad->entity); + index = pad->index; + } else { + sd = vin_to_source(vin); + index = vin->parallel.source_pad; + } + + fmt.pad = index; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + rect->left = rect->top = 0; + rect->width = fmt.format.width; + rect->height = fmt.format.height; + + if (fmt.format.field == V4L2_FIELD_ALTERNATE) { + switch (vin->format.field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + rect->height *= 2; + break; + } + } + + return 0; +} + static int rvin_g_selection(struct file *file, void *fh, struct v4l2_selection *s) { struct rvin_dev *vin = video_drvdata(file); + int ret; + + if (!vin->scaler) + return -ENOIOCTLCMD; if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; @@ -439,9 +486,10 @@ static int rvin_g_selection(struct file *file, void *fh, switch (s->target) { case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_CROP_DEFAULT: - s->r.left = s->r.top = 0; - s->r.width = vin->src_rect.width; - s->r.height = vin->src_rect.height; + ret = rvin_remote_rectangle(vin, &s->r); + if (ret) + return ret; + break; case V4L2_SEL_TGT_CROP: s->r = vin->crop; @@ -473,6 +521,10 @@ static int rvin_s_selection(struct file *file, void *fh, .width = 6, .height = 2, }; + int ret; + + if (!vin->scaler) + return -ENOIOCTLCMD; if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; @@ -482,23 +534,23 @@ static int rvin_s_selection(struct file *file, void *fh, switch (s->target) { case V4L2_SEL_TGT_CROP: /* Can't crop outside of source input */ - max_rect.top = max_rect.left = 0; - max_rect.width = vin->src_rect.width; - max_rect.height = vin->src_rect.height; + ret = rvin_remote_rectangle(vin, &max_rect); + if (ret) + return ret; + v4l2_rect_map_inside(&r, &max_rect); - v4l_bound_align_image(&r.width, 6, vin->src_rect.width, 0, - &r.height, 2, vin->src_rect.height, 0, 0); + v4l_bound_align_image(&r.width, 6, max_rect.width, 0, + &r.height, 2, max_rect.height, 0, 0); - r.top = clamp_t(s32, r.top, 0, - vin->src_rect.height - r.height); - r.left = clamp_t(s32, r.left, 0, vin->src_rect.width - r.width); + r.top = clamp_t(s32, r.top, 0, max_rect.height - r.height); + r.left = clamp_t(s32, r.left, 0, max_rect.width - r.width); vin->crop = s->r = r; vin_dbg(vin, "Cropped %dx%d@%d:%d of %dx%d\n", r.width, r.height, r.left, r.top, - vin->src_rect.width, vin->src_rect.height); + max_rect.width, max_rect.height); break; case V4L2_SEL_TGT_COMPOSE: /* Make sure compose rect fits inside output format */ @@ -866,6 +918,9 @@ static const struct v4l2_ioctl_ops rvin_mc_ioctl_ops = { .vidioc_s_fmt_vid_cap = rvin_mc_s_fmt_vid_cap, .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap, + .vidioc_g_selection = rvin_g_selection, + .vidioc_s_selection = rvin_s_selection, + .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_querybuf = vb2_ioctl_querybuf, diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index 1f94589d9ef1..cb206d3976dd 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -31,6 +31,7 @@ /* Max number on VIN instances that can be in a system */ #define RCAR_VIN_NUM 32 +struct rvin_dev; struct rvin_group; enum model_id { @@ -155,6 +156,7 @@ struct rvin_group_route { * @max_height: max input height the VIN supports * @routes: list of possible routes from the CSI-2 recivers to * all VINs. The list mush be NULL terminated. + * @scaler: Optional scaler */ struct rvin_info { enum model_id model; @@ -165,6 +167,7 @@ struct rvin_info { unsigned int max_width; unsigned int max_height; const struct rvin_group_route *routes; + void (*scaler)(struct rvin_dev *vin); }; /** @@ -203,7 +206,7 @@ struct rvin_info { * * @crop: active cropping * @compose: active composing - * @src_rect: active size of the video source + * @scaler: Optional scaler * @std: active video standard of the video source * * @alpha: Alpha component to fill in for supported pixel formats @@ -247,7 +250,7 @@ struct rvin_dev { struct v4l2_rect crop; struct v4l2_rect compose; - struct v4l2_rect src_rect; + void (*scaler)(struct rvin_dev *vin); v4l2_std_id std; unsigned int alpha; @@ -304,6 +307,8 @@ const struct rvin_video_format *rvin_format_from_pixel(struct rvin_dev *vin, /* Cropping, composing and scaling */ +void rvin_scaler_gen2(struct rvin_dev *vin); +void rvin_scaler_gen3(struct rvin_dev *vin); void rvin_crop_scale_comp(struct rvin_dev *vin); int rvin_set_channel_routing(struct rvin_dev *vin, u8 chsel); diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig new file mode 100644 index 000000000000..b39818c1f053 --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 + +config VIDEO_RZG2L_CSI2 + tristate "RZ/G2L MIPI CSI-2 Receiver" + depends on ARCH_RENESAS || COMPILE_TEST + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + select MEDIA_CONTROLLER + select RESET_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Support for Renesas RZ/G2L (and alike SoC's) MIPI CSI-2 + Receiver driver. + + To compile this driver as a module, choose M here: the + module will be called rzg2l-csi2. + +config VIDEO_RZG2L_CRU + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" + depends on ARCH_RENESAS || COMPILE_TEST + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEOBUF2_DMA_CONTIG + select VIDEO_V4L2_SUBDEV_API + help + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving + Unit (CRU) driver. + + To compile this driver as a module, choose M here: the + module will be called rzg2l-cru. diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile new file mode 100644 index 000000000000..c4db2632874f --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o + +rzg2l-cru-objs = rzg2l-core.o rzg2l-ip.o rzg2l-video.o +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c new file mode 100644 index 000000000000..5939f5165a5e --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + * + * Based on Renesas R-Car VIN + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> + +#include "rzg2l-cru.h" + +static inline struct rzg2l_cru_dev *notifier_to_cru(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rzg2l_cru_dev, notifier); +} + +static const struct media_device_ops rzg2l_cru_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +/* ----------------------------------------------------------------------------- + * Group async notifier + */ + +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); + struct media_entity *source, *sink; + int ret; + + ret = rzg2l_cru_ip_subdev_register(cru); + if (ret) + return ret; + + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); + if (ret) { + dev_err(cru->dev, "Failed to register subdev nodes\n"); + return ret; + } + + ret = rzg2l_cru_video_register(cru); + if (ret) + return ret; + + /* + * CRU can be connected either to CSI2 or PARALLEL device + * For now we are only supporting CSI2 + * + * Create media device link between CSI-2 <-> CRU IP + */ + source = &cru->csi.subdev->entity; + sink = &cru->ip.subdev.entity; + ret = media_create_pad_link(source, 1, sink, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(cru->dev, "Error creating link from %s to %s\n", + source->name, sink->name); + return ret; + } + cru->csi.channel = 0; + cru->ip.remote = cru->csi.subdev; + + /* Create media device link between CRU IP <-> CRU OUTPUT */ + source = &cru->ip.subdev.entity; + sink = &cru->vdev.entity; + ret = media_create_pad_link(source, 1, sink, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(cru->dev, "Error creating link from %s to %s\n", + source->name, sink->name); + return ret; + } + + return 0; +} + +static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); + + rzg2l_cru_ip_subdev_unregister(cru); + + mutex_lock(&cru->mdev_lock); + + if (cru->csi.asd == asd) { + cru->csi.subdev = NULL; + dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); + } + + mutex_unlock(&cru->mdev_lock); +} + +static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); + + mutex_lock(&cru->mdev_lock); + + if (cru->csi.asd == asd) { + cru->csi.subdev = subdev; + dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); + } + + mutex_unlock(&cru->mdev_lock); + + return 0; +} + +static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { + .bound = rzg2l_cru_group_notify_bound, + .unbind = rzg2l_cru_group_notify_unbind, + .complete = rzg2l_cru_group_notify_complete, +}; + +static int rzg2l_cru_mc_parse_of(struct rzg2l_cru_dev *cru) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *ep, *fwnode; + struct v4l2_async_subdev *asd; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, 0, 0); + if (!ep) + return 0; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + fwnode_handle_put(ep); + if (ret) { + dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); + ret = -EINVAL; + goto out; + } + + if (!of_device_is_available(to_of_node(fwnode))) { + dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", + to_of_node(fwnode)); + ret = -ENOTCONN; + goto out; + } + + asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + cru->csi.asd = asd; + + dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", + to_of_node(fwnode), vep.base.id); +out: + fwnode_handle_put(fwnode); + + return ret; +} + +static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) +{ + int ret; + + v4l2_async_nf_init(&cru->notifier); + + ret = rzg2l_cru_mc_parse_of(cru); + if (ret) + return ret; + + cru->notifier.ops = &rzg2l_cru_async_ops; + + if (list_empty(&cru->notifier.asd_list)) + return 0; + + ret = v4l2_async_nf_register(&cru->v4l2_dev, &cru->notifier); + if (ret < 0) { + dev_err(cru->dev, "Notifier registration failed\n"); + v4l2_async_nf_cleanup(&cru->notifier); + return ret; + } + + return 0; +} + +static int rzg2l_cru_media_init(struct rzg2l_cru_dev *cru) +{ + struct media_device *mdev = NULL; + const struct of_device_id *match; + int ret; + + cru->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); + if (ret) + return ret; + + mutex_init(&cru->mdev_lock); + mdev = &cru->mdev; + mdev->dev = cru->dev; + mdev->ops = &rzg2l_cru_media_ops; + + match = of_match_node(cru->dev->driver->of_match_table, + cru->dev->of_node); + + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); + + cru->v4l2_dev.mdev = &cru->mdev; + + media_device_init(mdev); + + ret = rzg2l_cru_mc_parse_of_graph(cru); + if (ret) { + mutex_lock(&cru->mdev_lock); + cru->v4l2_dev.mdev = NULL; + mutex_unlock(&cru->mdev_lock); + } + + return 0; +} + +static int rzg2l_cru_probe(struct platform_device *pdev) +{ + struct rzg2l_cru_dev *cru; + int ret; + + cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); + if (!cru) + return -ENOMEM; + + cru->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(cru->base)) + return PTR_ERR(cru->base); + + cru->presetn = devm_reset_control_get_shared(&pdev->dev, "presetn"); + if (IS_ERR(cru->presetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), + "Failed to get cpg presetn\n"); + + cru->aresetn = devm_reset_control_get_exclusive(&pdev->dev, "aresetn"); + if (IS_ERR(cru->aresetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), + "Failed to get cpg aresetn\n"); + + cru->vclk = devm_clk_get(&pdev->dev, "video"); + if (IS_ERR(cru->vclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(cru->vclk), + "Failed to get video clock\n"); + + cru->dev = &pdev->dev; + cru->info = of_device_get_match_data(&pdev->dev); + + cru->image_conv_irq = platform_get_irq(pdev, 0); + if (cru->image_conv_irq < 0) + return cru->image_conv_irq; + + platform_set_drvdata(pdev, cru); + + ret = rzg2l_cru_dma_register(cru); + if (ret) + return ret; + + cru->num_buf = RZG2L_CRU_HW_BUFFER_DEFAULT; + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + + ret = rzg2l_cru_media_init(cru); + if (ret) + goto error_dma_unregister; + + return 0; + +error_dma_unregister: + rzg2l_cru_dma_unregister(cru); + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int rzg2l_cru_remove(struct platform_device *pdev) +{ + struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + v4l2_async_nf_unregister(&cru->notifier); + v4l2_async_nf_cleanup(&cru->notifier); + + rzg2l_cru_video_unregister(cru); + media_device_cleanup(&cru->mdev); + mutex_destroy(&cru->mdev_lock); + + rzg2l_cru_dma_unregister(cru); + + return 0; +} + +static const struct of_device_id rzg2l_cru_of_id_table[] = { + { .compatible = "renesas,rzg2l-cru", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); + +static struct platform_driver rzg2l_cru_driver = { + .driver = { + .name = "rzg2l-cru", + .of_match_table = rzg2l_cru_of_id_table, + }, + .probe = rzg2l_cru_probe, + .remove = rzg2l_cru_remove, +}; + +module_platform_driver(rzg2l_cru_driver); + +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h new file mode 100644 index 000000000000..0b682cbae3eb --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + */ + +#ifndef __RZG2L_CRU__ +#define __RZG2L_CRU__ + +#include <linux/reset.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/videobuf2-v4l2.h> + +/* Number of HW buffers */ +#define RZG2L_CRU_HW_BUFFER_MAX 8 +#define RZG2L_CRU_HW_BUFFER_DEFAULT 3 + +/* Address alignment mask for HW buffers */ +#define RZG2L_CRU_HW_BUFFER_MASK 0x1ff + +/* Maximum number of CSI2 virtual channels */ +#define RZG2L_CRU_CSI2_VCHANNEL 4 + +#define RZG2L_CRU_MIN_INPUT_WIDTH 320 +#define RZG2L_CRU_MAX_INPUT_WIDTH 2800 +#define RZG2L_CRU_MIN_INPUT_HEIGHT 240 +#define RZG2L_CRU_MAX_INPUT_HEIGHT 4095 + +/** + * enum rzg2l_cru_dma_state - DMA states + * @RZG2L_CRU_DMA_STOPPED: No operation in progress + * @RZG2L_CRU_DMA_STARTING: Capture starting up + * @RZG2L_CRU_DMA_RUNNING: Operation in progress have buffers + * @RZG2L_CRU_DMA_STOPPING: Stopping operation + */ +enum rzg2l_cru_dma_state { + RZG2L_CRU_DMA_STOPPED = 0, + RZG2L_CRU_DMA_STARTING, + RZG2L_CRU_DMA_RUNNING, + RZG2L_CRU_DMA_STOPPING, +}; + +struct rzg2l_cru_csi { + struct v4l2_async_subdev *asd; + struct v4l2_subdev *subdev; + u32 channel; +}; + +struct rzg2l_cru_ip { + struct v4l2_subdev subdev; + struct media_pad pads[2]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *remote; +}; + +/** + * struct rzg2l_cru_dev - Renesas CRU device structure + * @dev: (OF) device + * @base: device I/O register space remapped to virtual memory + * @info: info about CRU instance + * + * @presetn: CRU_PRESETN reset line + * @aresetn: CRU_ARESETN reset line + * + * @vclk: CRU Main clock + * + * @image_conv_irq: Holds image conversion interrupt number + * + * @vdev: V4L2 video device associated with CRU + * @v4l2_dev: V4L2 device + * @num_buf: Holds the current number of buffers enabled + * @notifier: V4L2 asynchronous subdevs notifier + * + * @ip: Image processing subdev info + * @csi: CSI info + * @mdev: media device + * @mdev_lock: protects the count, notifier and csi members + * @pad: media pad for the video device entity + * + * @lock: protects @queue + * @queue: vb2 buffers queue + * @scratch: cpu address for scratch buffer + * @scratch_phys: physical address of the scratch buffer + * + * @qlock: protects @queue_buf, @buf_list, @sequence + * @state + * @queue_buf: Keeps track of buffers given to HW slot + * @buf_list: list of queued buffers + * @sequence: V4L2 buffers sequence number + * @state: keeps track of operation state + * + * @format: active V4L2 pixel format + */ +struct rzg2l_cru_dev { + struct device *dev; + void __iomem *base; + const struct rzg2l_cru_info *info; + + struct reset_control *presetn; + struct reset_control *aresetn; + + struct clk *vclk; + + int image_conv_irq; + + struct video_device vdev; + struct v4l2_device v4l2_dev; + u8 num_buf; + + struct v4l2_async_notifier notifier; + + struct rzg2l_cru_ip ip; + struct rzg2l_cru_csi csi; + struct media_device mdev; + struct mutex mdev_lock; + struct media_pad pad; + + struct mutex lock; + struct vb2_queue queue; + void *scratch; + dma_addr_t scratch_phys; + + spinlock_t qlock; + struct vb2_v4l2_buffer *queue_buf[RZG2L_CRU_HW_BUFFER_MAX]; + struct list_head buf_list; + unsigned int sequence; + enum rzg2l_cru_dma_state state; + + struct v4l2_pix_format format; +}; + +void rzg2l_cru_vclk_unprepare(struct rzg2l_cru_dev *cru); +int rzg2l_cru_vclk_prepare(struct rzg2l_cru_dev *cru); + +int rzg2l_cru_start_image_processing(struct rzg2l_cru_dev *cru); +void rzg2l_cru_stop_image_processing(struct rzg2l_cru_dev *cru); + +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru); +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru); + +int rzg2l_cru_video_register(struct rzg2l_cru_dev *cru); +void rzg2l_cru_video_unregister(struct rzg2l_cru_dev *cru); + +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format); + +int rzg2l_cru_ip_subdev_register(struct rzg2l_cru_dev *cru); +void rzg2l_cru_ip_subdev_unregister(struct rzg2l_cru_dev *cru); +struct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru); + +#endif diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c new file mode 100644 index 000000000000..33e08efa3039 --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Renesas RZ/G2L MIPI CSI-2 Receiver + * + * Copyright (C) 2022 Renesas Electronics Corp. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/sys_soc.h> +#include <linux/units.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> + +/* LINK registers */ +/* Module Configuration Register */ +#define CSI2nMCG 0x0 +#define CSI2nMCG_SDLN GENMASK(11, 8) + +/* Module Control Register 0 */ +#define CSI2nMCT0 0x10 +#define CSI2nMCT0_VDLN(x) ((x) << 0) + +/* Module Control Register 2 */ +#define CSI2nMCT2 0x18 +#define CSI2nMCT2_FRRSKW(x) ((x) << 16) +#define CSI2nMCT2_FRRCLK(x) ((x) << 0) + +/* Module Control Register 3 */ +#define CSI2nMCT3 0x1c +#define CSI2nMCT3_RXEN BIT(0) + +/* Reset Control Register */ +#define CSI2nRTCT 0x28 +#define CSI2nRTCT_VSRST BIT(0) + +/* Reset Status Register */ +#define CSI2nRTST 0x2c +#define CSI2nRTST_VSRSTS BIT(0) + +/* Receive Data Type Enable Low Register */ +#define CSI2nDTEL 0x60 + +/* Receive Data Type Enable High Register */ +#define CSI2nDTEH 0x64 + +/* DPHY registers */ +/* D-PHY Control Register 0 */ +#define CSIDPHYCTRL0 0x400 +#define CSIDPHYCTRL0_EN_LDO1200 BIT(1) +#define CSIDPHYCTRL0_EN_BGR BIT(0) + +/* D-PHY Timing Register 0 */ +#define CSIDPHYTIM0 0x404 +#define CSIDPHYTIM0_TCLK_MISS(x) ((x) << 24) +#define CSIDPHYTIM0_T_INIT(x) ((x) << 0) + +/* D-PHY Timing Register 1 */ +#define CSIDPHYTIM1 0x408 +#define CSIDPHYTIM1_THS_PREPARE(x) ((x) << 24) +#define CSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16) +#define CSIDPHYTIM1_THS_SETTLE(x) ((x) << 8) +#define CSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0) + +/* D-PHY Skew Adjustment Function */ +#define CSIDPHYSKW0 0x460 +#define CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(x) ((x) & 0x3) +#define CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(x) (((x) & 0x3) << 4) +#define CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(x) (((x) & 0x3) << 8) +#define CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(x) (((x) & 0x3) << 12) +#define CSIDPHYSKW0_DEFAULT_SKW CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(1) | \ + CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(1) | \ + CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(1) | \ + CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(1) + +#define VSRSTS_RETRIES 20 + +#define RZG2L_CSI2_MIN_WIDTH 320 +#define RZG2L_CSI2_MIN_HEIGHT 240 +#define RZG2L_CSI2_MAX_WIDTH 2800 +#define RZG2L_CSI2_MAX_HEIGHT 4095 + +#define RZG2L_CSI2_DEFAULT_WIDTH RZG2L_CSI2_MIN_WIDTH +#define RZG2L_CSI2_DEFAULT_HEIGHT RZG2L_CSI2_MIN_HEIGHT +#define RZG2L_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_UYVY8_1X16 + +enum rzg2l_csi2_pads { + RZG2L_CSI2_SINK = 0, + RZG2L_CSI2_SOURCE, + NR_OF_RZG2L_CSI2_PAD, +}; + +struct rzg2l_csi2 { + struct device *dev; + void __iomem *base; + struct reset_control *presetn; + struct reset_control *cmn_rstb; + struct clk *sysclk; + unsigned long vclk_rate; + + struct v4l2_subdev subdev; + struct media_pad pads[NR_OF_RZG2L_CSI2_PAD]; + + struct v4l2_async_notifier notifier; + struct v4l2_subdev *remote_source; + + unsigned short lanes; + unsigned long hsfreq; + + bool dphy_enabled; +}; + +struct rzg2l_csi2_timings { + u32 t_init; + u32 tclk_miss; + u32 tclk_settle; + u32 ths_settle; + u32 tclk_prepare; + u32 ths_prepare; + u32 max_hsfreq; +}; + +static const struct rzg2l_csi2_timings rzg2l_csi2_global_timings[] = { + { + .max_hsfreq = 80, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 23, + .ths_settle = 31, + .tclk_prepare = 10, + .ths_prepare = 19, + }, + { + .max_hsfreq = 125, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 23, + .ths_settle = 28, + .tclk_prepare = 10, + .ths_prepare = 19, + }, + { + .max_hsfreq = 250, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 23, + .ths_settle = 22, + .tclk_prepare = 10, + .ths_prepare = 16, + }, + { + .max_hsfreq = 360, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 18, + .ths_settle = 19, + .tclk_prepare = 10, + .ths_prepare = 10, + }, + { + .max_hsfreq = 1500, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 18, + .ths_settle = 18, + .tclk_prepare = 10, + .ths_prepare = 10, + }, +}; + +struct rzg2l_csi2_format { + u32 code; + unsigned int datatype; + unsigned int bpp; +}; + +static const struct rzg2l_csi2_format rzg2l_csi2_formats[] = { + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, +}; + +static inline struct rzg2l_csi2 *sd_to_csi2(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rzg2l_csi2, subdev); +} + +static const struct rzg2l_csi2_format *rzg2l_csi2_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_formats); i++) + if (rzg2l_csi2_formats[i].code == code) + return &rzg2l_csi2_formats[i]; + + return NULL; +} + +static inline struct rzg2l_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rzg2l_csi2, notifier); +} + +static u32 rzg2l_csi2_read(struct rzg2l_csi2 *csi2, unsigned int reg) +{ + return ioread32(csi2->base + reg); +} + +static void rzg2l_csi2_write(struct rzg2l_csi2 *csi2, unsigned int reg, + u32 data) +{ + iowrite32(data, csi2->base + reg); +} + +static void rzg2l_csi2_set(struct rzg2l_csi2 *csi2, unsigned int reg, u32 set) +{ + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) | set); +} + +static void rzg2l_csi2_clr(struct rzg2l_csi2 *csi2, unsigned int reg, u32 clr) +{ + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) & ~clr); +} + +static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) +{ + struct v4l2_subdev *source = csi2->remote_source; + const struct rzg2l_csi2_format *format; + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; + struct v4l2_ctrl *ctrl; + u64 mbps; + + /* Read the pixel rate control from remote. */ + ctrl = v4l2_ctrl_find(source->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (!ctrl) { + dev_err(csi2->dev, "no pixel rate control in subdev %s\n", + source->name); + return -EINVAL; + } + + state = v4l2_subdev_lock_and_get_active_state(&csi2->subdev); + fmt = v4l2_subdev_get_pad_format(&csi2->subdev, state, RZG2L_CSI2_SINK); + format = rzg2l_csi2_code_to_fmt(fmt->code); + v4l2_subdev_unlock_state(state); + + /* + * Calculate hsfreq in Mbps + * hsfreq = (pixel_rate * bits_per_sample) / number_of_lanes + */ + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * format->bpp; + do_div(mbps, csi2->lanes * 1000000); + + return mbps; +} + +/* ----------------------------------------------------------------------------- + * DPHY setting + */ + +static int rzg2l_csi2_dphy_disable(struct rzg2l_csi2 *csi2) +{ + int ret; + + /* Reset the CRU (D-PHY) */ + ret = reset_control_assert(csi2->cmn_rstb); + if (ret) + return ret; + + /* Stop the D-PHY clock */ + clk_disable_unprepare(csi2->sysclk); + + /* Cancel the EN_LDO1200 register setting */ + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); + + /* Cancel the EN_BGR register setting */ + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); + + csi2->dphy_enabled = false; + + return 0; +} + +static int rzg2l_csi2_dphy_enable(struct rzg2l_csi2 *csi2) +{ + const struct rzg2l_csi2_timings *dphy_timing; + u32 dphytim0, dphytim1; + unsigned int i; + int mbps; + int ret; + + mbps = rzg2l_csi2_calc_mbps(csi2); + if (mbps < 0) + return mbps; + + csi2->hsfreq = mbps; + + /* Set DPHY timing parameters */ + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_global_timings); ++i) { + dphy_timing = &rzg2l_csi2_global_timings[i]; + + if (csi2->hsfreq <= dphy_timing->max_hsfreq) + break; + } + + if (i >= ARRAY_SIZE(rzg2l_csi2_global_timings)) + return -EINVAL; + + /* Set D-PHY timing parameters */ + dphytim0 = CSIDPHYTIM0_TCLK_MISS(dphy_timing->tclk_miss) | + CSIDPHYTIM0_T_INIT(dphy_timing->t_init); + dphytim1 = CSIDPHYTIM1_THS_PREPARE(dphy_timing->ths_prepare) | + CSIDPHYTIM1_TCLK_PREPARE(dphy_timing->tclk_prepare) | + CSIDPHYTIM1_THS_SETTLE(dphy_timing->ths_settle) | + CSIDPHYTIM1_TCLK_SETTLE(dphy_timing->tclk_settle); + rzg2l_csi2_write(csi2, CSIDPHYTIM0, dphytim0); + rzg2l_csi2_write(csi2, CSIDPHYTIM1, dphytim1); + + /* Enable D-PHY power control 0 */ + rzg2l_csi2_write(csi2, CSIDPHYSKW0, CSIDPHYSKW0_DEFAULT_SKW); + + /* Set the EN_BGR bit */ + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); + + /* Delay 20us to be stable */ + usleep_range(20, 40); + + /* Enable D-PHY power control 1 */ + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); + + /* Delay 10us to be stable */ + usleep_range(10, 20); + + /* Start supplying the internal clock for the D-PHY block */ + ret = clk_prepare_enable(csi2->sysclk); + if (ret) + rzg2l_csi2_dphy_disable(csi2); + + csi2->dphy_enabled = true; + + return ret; +} + +static int rzg2l_csi2_dphy_setting(struct v4l2_subdev *sd, bool on) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + + if (on) + return rzg2l_csi2_dphy_enable(csi2); + + return rzg2l_csi2_dphy_disable(csi2); +} + +static void rzg2l_csi2_mipi_link_enable(struct rzg2l_csi2 *csi2) +{ + unsigned long vclk_rate = csi2->vclk_rate / HZ_PER_MHZ; + u32 frrskw, frrclk, frrskw_coeff, frrclk_coeff; + + /* Select data lanes */ + rzg2l_csi2_write(csi2, CSI2nMCT0, CSI2nMCT0_VDLN(csi2->lanes)); + + frrskw_coeff = 3 * vclk_rate * 8; + frrclk_coeff = frrskw_coeff / 2; + frrskw = DIV_ROUND_UP(frrskw_coeff, csi2->hsfreq); + frrclk = DIV_ROUND_UP(frrclk_coeff, csi2->hsfreq); + rzg2l_csi2_write(csi2, CSI2nMCT2, CSI2nMCT2_FRRSKW(frrskw) | + CSI2nMCT2_FRRCLK(frrclk)); + + /* + * Select data type. + * FS, FE, LS, LE, Generic Short Packet Codes 1 to 8, + * Generic Long Packet Data Types 1 to 4 YUV422 8-bit, + * RGB565, RGB888, RAW8 to RAW20, User-defined 8-bit + * data types 1 to 8 + */ + rzg2l_csi2_write(csi2, CSI2nDTEL, 0xf778ff0f); + rzg2l_csi2_write(csi2, CSI2nDTEH, 0x00ffff1f); + + /* Enable LINK reception */ + rzg2l_csi2_write(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); +} + +static void rzg2l_csi2_mipi_link_disable(struct rzg2l_csi2 *csi2) +{ + unsigned int timeout = VSRSTS_RETRIES; + + /* Stop LINK reception */ + rzg2l_csi2_clr(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); + + /* Request a software reset of the LINK Video Pixel Interface */ + rzg2l_csi2_write(csi2, CSI2nRTCT, CSI2nRTCT_VSRST); + + /* Make sure CSI2nRTST.VSRSTS bit is cleared */ + while (--timeout) { + if (!(rzg2l_csi2_read(csi2, CSI2nRTST) & CSI2nRTST_VSRSTS)) + break; + usleep_range(100, 200); + }; + + if (!timeout) + dev_err(csi2->dev, "Clearing CSI2nRTST.VSRSTS timed out\n"); +} + +static int rzg2l_csi2_mipi_link_setting(struct v4l2_subdev *sd, bool on) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + + if (on) + rzg2l_csi2_mipi_link_enable(csi2); + else + rzg2l_csi2_mipi_link_disable(csi2); + + return 0; +} + +static int rzg2l_csi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + int s_stream_ret = 0; + int ret; + + if (enable) { + ret = pm_runtime_resume_and_get(csi2->dev); + if (ret) + return ret; + + ret = rzg2l_csi2_mipi_link_setting(sd, 1); + if (ret) + goto err_pm_put; + + ret = reset_control_deassert(csi2->cmn_rstb); + if (ret) + goto err_mipi_link_disable; + } + + ret = v4l2_subdev_call(csi2->remote_source, video, s_stream, enable); + if (ret) + s_stream_ret = ret; + + if (enable && ret) + goto err_assert_rstb; + + if (!enable) { + ret = rzg2l_csi2_dphy_setting(sd, 0); + if (ret && !s_stream_ret) + s_stream_ret = ret; + ret = rzg2l_csi2_mipi_link_setting(sd, 0); + if (ret && !s_stream_ret) + s_stream_ret = ret; + + pm_runtime_put_sync(csi2->dev); + } + + return s_stream_ret; + +err_assert_rstb: + reset_control_assert(csi2->cmn_rstb); +err_mipi_link_disable: + rzg2l_csi2_mipi_link_setting(sd, 0); +err_pm_put: + pm_runtime_put_sync(csi2->dev); + return ret; +} + +static int rzg2l_csi2_pre_streamon(struct v4l2_subdev *sd, u32 flags) +{ + return rzg2l_csi2_dphy_setting(sd, 1); +} + +static int rzg2l_csi2_post_streamoff(struct v4l2_subdev *sd) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + + /* + * In ideal case D-PHY will be disabled in s_stream(0) callback + * as mentioned in the HW manual. The below will only happen when + * pre_streamon succeeds and further down the line s_stream(1) + * fails so we need to undo things in post_streamoff. + */ + if (csi2->dphy_enabled) + return rzg2l_csi2_dphy_setting(sd, 0); + + return 0; +} + +static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *src_format; + struct v4l2_mbus_framefmt *sink_format; + + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE); + if (fmt->pad == RZG2L_CSI2_SOURCE) { + fmt->format = *src_format; + return 0; + } + + sink_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); + + if (!rzg2l_csi2_code_to_fmt(fmt->format.code)) + sink_format->code = rzg2l_csi2_formats[0].code; + else + sink_format->code = fmt->format.code; + + sink_format->field = V4L2_FIELD_NONE; + sink_format->colorspace = fmt->format.colorspace; + sink_format->xfer_func = fmt->format.xfer_func; + sink_format->ycbcr_enc = fmt->format.ycbcr_enc; + sink_format->quantization = fmt->format.quantization; + sink_format->width = clamp_t(u32, fmt->format.width, + RZG2L_CSI2_MIN_WIDTH, RZG2L_CSI2_MAX_WIDTH); + sink_format->height = clamp_t(u32, fmt->format.height, + RZG2L_CSI2_MIN_HEIGHT, RZG2L_CSI2_MAX_HEIGHT); + fmt->format = *sink_format; + + /* propagate format to source pad */ + *src_format = *sink_format; + + return 0; +} + +static int rzg2l_csi2_init_config(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { .pad = RZG2L_CSI2_SINK, }; + + fmt.format.width = RZG2L_CSI2_DEFAULT_WIDTH; + fmt.format.height = RZG2L_CSI2_DEFAULT_HEIGHT; + fmt.format.field = V4L2_FIELD_NONE; + fmt.format.code = RZG2L_CSI2_DEFAULT_FMT; + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; + fmt.format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return rzg2l_csi2_set_format(sd, sd_state, &fmt); +} + +static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(rzg2l_csi2_formats)) + return -EINVAL; + + code->code = rzg2l_csi2_formats[code->index].code; + + return 0; +} + +static int rzg2l_csi2_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index != 0) + return -EINVAL; + + fse->min_width = RZG2L_CSI2_MIN_WIDTH; + fse->min_height = RZG2L_CSI2_MIN_HEIGHT; + fse->max_width = RZG2L_CSI2_MAX_WIDTH; + fse->max_height = RZG2L_CSI2_MAX_HEIGHT; + + return 0; +} + +static const struct v4l2_subdev_video_ops rzg2l_csi2_video_ops = { + .s_stream = rzg2l_csi2_s_stream, + .pre_streamon = rzg2l_csi2_pre_streamon, + .post_streamoff = rzg2l_csi2_post_streamoff, +}; + +static const struct v4l2_subdev_pad_ops rzg2l_csi2_pad_ops = { + .enum_mbus_code = rzg2l_csi2_enum_mbus_code, + .init_cfg = rzg2l_csi2_init_config, + .enum_frame_size = rzg2l_csi2_enum_frame_size, + .set_fmt = rzg2l_csi2_set_format, + .get_fmt = v4l2_subdev_get_fmt, +}; + +static const struct v4l2_subdev_ops rzg2l_csi2_subdev_ops = { + .video = &rzg2l_csi2_video_ops, + .pad = &rzg2l_csi2_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Async handling and registration of subdevices and links. + */ + +static int rzg2l_csi2_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); + + csi2->remote_source = subdev; + + dev_dbg(csi2->dev, "Bound subdev: %s pad\n", subdev->name); + + return media_create_pad_link(&subdev->entity, RZG2L_CSI2_SINK, + &csi2->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void rzg2l_csi2_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); + + csi2->remote_source = NULL; + + dev_dbg(csi2->dev, "Unbind subdev %s\n", subdev->name); +} + +static const struct v4l2_async_notifier_operations rzg2l_csi2_notify_ops = { + .bound = rzg2l_csi2_notify_bound, + .unbind = rzg2l_csi2_notify_unbind, +}; + +static int rzg2l_csi2_parse_v4l2(struct rzg2l_csi2 *csi2, + struct v4l2_fwnode_endpoint *vep) +{ + /* Only port 0 endpoint 0 is valid. */ + if (vep->base.port || vep->base.id) + return -ENOTCONN; + + csi2->lanes = vep->bus.mipi_csi2.num_data_lanes; + + return 0; +} + +static int rzg2l_csi2_parse_dt(struct rzg2l_csi2 *csi2) +{ + struct v4l2_fwnode_endpoint v4l2_ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi2->dev), 0, 0, 0); + if (!ep) { + dev_err(csi2->dev, "Not connected to subdevice\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); + if (ret) { + dev_err(csi2->dev, "Could not parse v4l2 endpoint\n"); + fwnode_handle_put(ep); + return -EINVAL; + } + + ret = rzg2l_csi2_parse_v4l2(csi2, &v4l2_ep); + if (ret) { + fwnode_handle_put(ep); + return ret; + } + + fwnode = fwnode_graph_get_remote_endpoint(ep); + fwnode_handle_put(ep); + + v4l2_async_nf_init(&csi2->notifier); + csi2->notifier.ops = &rzg2l_csi2_notify_ops; + + asd = v4l2_async_nf_add_fwnode(&csi2->notifier, fwnode, + struct v4l2_async_subdev); + fwnode_handle_put(fwnode); + if (IS_ERR(asd)) + return PTR_ERR(asd); + + ret = v4l2_async_subdev_nf_register(&csi2->subdev, &csi2->notifier); + if (ret) + v4l2_async_nf_cleanup(&csi2->notifier); + + return ret; +} + +static int rzg2l_validate_csi2_lanes(struct rzg2l_csi2 *csi2) +{ + int lanes; + int ret; + + if (csi2->lanes != 1 && csi2->lanes != 2 && csi2->lanes != 4) { + dev_err(csi2->dev, "Unsupported number of data-lanes: %u\n", + csi2->lanes); + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(csi2->dev); + if (ret) + return ret; + + /* Checking the maximum lanes support for CSI-2 module */ + lanes = (rzg2l_csi2_read(csi2, CSI2nMCG) & CSI2nMCG_SDLN) >> 8; + if (lanes < csi2->lanes) { + dev_err(csi2->dev, + "Failed to support %d data lanes\n", csi2->lanes); + ret = -EINVAL; + } + + pm_runtime_put_sync(csi2->dev); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver. + */ + +static const struct media_entity_operations rzg2l_csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int rzg2l_csi2_probe(struct platform_device *pdev) +{ + struct rzg2l_csi2 *csi2; + struct clk *vclk; + int ret; + + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); + if (!csi2) + return -ENOMEM; + + csi2->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2->base)) + return PTR_ERR(csi2->base); + + csi2->cmn_rstb = devm_reset_control_get_exclusive(&pdev->dev, "cmn-rstb"); + if (IS_ERR(csi2->cmn_rstb)) + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->cmn_rstb), + "Failed to get cpg cmn-rstb\n"); + + csi2->presetn = devm_reset_control_get_shared(&pdev->dev, "presetn"); + if (IS_ERR(csi2->presetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->presetn), + "Failed to get cpg presetn\n"); + + csi2->sysclk = devm_clk_get(&pdev->dev, "system"); + if (IS_ERR(csi2->sysclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->sysclk), + "Failed to get system clk\n"); + + vclk = clk_get(&pdev->dev, "video"); + if (IS_ERR(vclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(vclk), + "Failed to get video clock\n"); + csi2->vclk_rate = clk_get_rate(vclk); + clk_put(vclk); + + csi2->dev = &pdev->dev; + + platform_set_drvdata(pdev, csi2); + + ret = rzg2l_csi2_parse_dt(csi2); + if (ret) + return ret; + + pm_runtime_enable(&pdev->dev); + + ret = rzg2l_validate_csi2_lanes(csi2); + if (ret) + goto error_pm; + + csi2->subdev.dev = &pdev->dev; + v4l2_subdev_init(&csi2->subdev, &rzg2l_csi2_subdev_ops); + v4l2_set_subdevdata(&csi2->subdev, &pdev->dev); + snprintf(csi2->subdev.name, sizeof(csi2->subdev.name), + "csi-%s", dev_name(&pdev->dev)); + csi2->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + csi2->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2->subdev.entity.ops = &rzg2l_csi2_entity_ops; + + csi2->pads[RZG2L_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; + /* + * TODO: RZ/G2L CSI2 supports 4 virtual channels, as virtual + * channels should be implemented by streams API which is under + * development lets hardcode to VC0 for now. + */ + csi2->pads[RZG2L_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&csi2->subdev.entity, 2, csi2->pads); + if (ret) + goto error_pm; + + ret = v4l2_subdev_init_finalize(&csi2->subdev); + if (ret < 0) + goto error_async; + + ret = v4l2_async_register_subdev(&csi2->subdev); + if (ret < 0) + goto error_subdev; + + return 0; + +error_subdev: + v4l2_subdev_cleanup(&csi2->subdev); +error_async: + v4l2_async_nf_unregister(&csi2->notifier); + v4l2_async_nf_cleanup(&csi2->notifier); + media_entity_cleanup(&csi2->subdev.entity); +error_pm: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int rzg2l_csi2_remove(struct platform_device *pdev) +{ + struct rzg2l_csi2 *csi2 = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&csi2->notifier); + v4l2_async_nf_cleanup(&csi2->notifier); + v4l2_async_unregister_subdev(&csi2->subdev); + v4l2_subdev_cleanup(&csi2->subdev); + media_entity_cleanup(&csi2->subdev.entity); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused rzg2l_csi2_pm_runtime_suspend(struct device *dev) +{ + struct rzg2l_csi2 *csi2 = dev_get_drvdata(dev); + + reset_control_assert(csi2->presetn); + + return 0; +} + +static int __maybe_unused rzg2l_csi2_pm_runtime_resume(struct device *dev) +{ + struct rzg2l_csi2 *csi2 = dev_get_drvdata(dev); + + return reset_control_deassert(csi2->presetn); +} + +static const struct dev_pm_ops rzg2l_csi2_pm_ops = { + SET_RUNTIME_PM_OPS(rzg2l_csi2_pm_runtime_suspend, rzg2l_csi2_pm_runtime_resume, NULL) +}; + +static const struct of_device_id rzg2l_csi2_of_table[] = { + { .compatible = "renesas,rzg2l-csi2", }, + { /* sentinel */ } +}; + +static struct platform_driver rzg2l_csi2_pdrv = { + .remove = rzg2l_csi2_remove, + .probe = rzg2l_csi2_probe, + .driver = { + .name = "rzg2l-csi2", + .of_match_table = rzg2l_csi2_of_table, + .pm = &rzg2l_csi2_pm_ops, + }, +}; + +module_platform_driver(rzg2l_csi2_pdrv); + +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/G2L MIPI CSI2 receiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c new file mode 100644 index 000000000000..4dcd2faff5bb --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + */ + +#include "rzg2l-cru.h" + +struct rzg2l_cru_ip_format { + u32 code; + unsigned int datatype; + unsigned int bpp; +}; + +static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, +}; + +enum rzg2l_csi2_pads { + RZG2L_CRU_IP_SINK = 0, + RZG2L_CRU_IP_SOURCE, +}; + +static const struct rzg2l_cru_ip_format *rzg2l_cru_ip_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_ip_formats); i++) + if (rzg2l_cru_ip_formats[i].code == code) + return &rzg2l_cru_ip_formats[i]; + + return NULL; +} + +struct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru) +{ + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + + state = v4l2_subdev_lock_and_get_active_state(&cru->ip.subdev); + fmt = v4l2_subdev_get_pad_format(&cru->ip.subdev, state, 1); + v4l2_subdev_unlock_state(state); + + return fmt; +} + +static int rzg2l_cru_ip_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rzg2l_cru_dev *cru; + int s_stream_ret = 0; + int ret; + + cru = v4l2_get_subdevdata(sd); + + if (!enable) { + ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable); + if (ret) + s_stream_ret = ret; + + ret = v4l2_subdev_call(cru->ip.remote, video, post_streamoff); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret && !s_stream_ret) + s_stream_ret = ret; + rzg2l_cru_stop_image_processing(cru); + } else { + ret = v4l2_subdev_call(cru->ip.remote, video, pre_streamon, 0); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + return ret; + + ret = rzg2l_cru_start_image_processing(cru); + if (ret) { + v4l2_subdev_call(cru->ip.remote, video, post_streamoff); + return ret; + } + + rzg2l_cru_vclk_unprepare(cru); + + ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (!ret) { + ret = rzg2l_cru_vclk_prepare(cru); + if (!ret) + return 0; + } else { + /* enable back vclk so that s_stream in error path disables it */ + if (rzg2l_cru_vclk_prepare(cru)) + dev_err(cru->dev, "Failed to enable vclk\n"); + } + + s_stream_ret = ret; + + v4l2_subdev_call(cru->ip.remote, video, post_streamoff); + rzg2l_cru_stop_image_processing(cru); + } + + return s_stream_ret; +} + +static int rzg2l_cru_ip_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *src_format; + struct v4l2_mbus_framefmt *sink_format; + + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CRU_IP_SOURCE); + if (fmt->pad == RZG2L_CRU_IP_SOURCE) { + fmt->format = *src_format; + return 0; + } + + sink_format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + if (!rzg2l_cru_ip_code_to_fmt(fmt->format.code)) + sink_format->code = rzg2l_cru_ip_formats[0].code; + else + sink_format->code = fmt->format.code; + + sink_format->field = V4L2_FIELD_NONE; + sink_format->colorspace = fmt->format.colorspace; + sink_format->xfer_func = fmt->format.xfer_func; + sink_format->ycbcr_enc = fmt->format.ycbcr_enc; + sink_format->quantization = fmt->format.quantization; + sink_format->width = clamp_t(u32, fmt->format.width, + RZG2L_CRU_MIN_INPUT_WIDTH, RZG2L_CRU_MAX_INPUT_WIDTH); + sink_format->height = clamp_t(u32, fmt->format.height, + RZG2L_CRU_MIN_INPUT_HEIGHT, RZG2L_CRU_MAX_INPUT_HEIGHT); + + fmt->format = *sink_format; + + /* propagate format to source pad */ + *src_format = *sink_format; + + return 0; +} + +static int rzg2l_cru_ip_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(rzg2l_cru_ip_formats)) + return -EINVAL; + + code->code = rzg2l_cru_ip_formats[code->index].code; + return 0; +} + +static int rzg2l_cru_ip_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index != 0) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_UYVY8_1X16) + return -EINVAL; + + fse->min_width = RZG2L_CRU_MIN_INPUT_WIDTH; + fse->min_height = RZG2L_CRU_MIN_INPUT_HEIGHT; + fse->max_width = RZG2L_CRU_MAX_INPUT_WIDTH; + fse->max_height = RZG2L_CRU_MAX_INPUT_HEIGHT; + + return 0; +} + +static int rzg2l_cru_ip_init_config(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { .pad = RZG2L_CRU_IP_SINK, }; + + fmt.format.width = RZG2L_CRU_MIN_INPUT_WIDTH; + fmt.format.height = RZG2L_CRU_MIN_INPUT_HEIGHT; + fmt.format.field = V4L2_FIELD_NONE; + fmt.format.code = MEDIA_BUS_FMT_UYVY8_1X16; + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; + fmt.format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return rzg2l_cru_ip_set_format(sd, sd_state, &fmt); +} + +static const struct v4l2_subdev_video_ops rzg2l_cru_ip_video_ops = { + .s_stream = rzg2l_cru_ip_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rzg2l_cru_ip_pad_ops = { + .enum_mbus_code = rzg2l_cru_ip_enum_mbus_code, + .enum_frame_size = rzg2l_cru_ip_enum_frame_size, + .init_cfg = rzg2l_cru_ip_init_config, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = rzg2l_cru_ip_set_format, +}; + +static const struct v4l2_subdev_ops rzg2l_cru_ip_subdev_ops = { + .video = &rzg2l_cru_ip_video_ops, + .pad = &rzg2l_cru_ip_pad_ops, +}; + +static const struct media_entity_operations rzg2l_cru_ip_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +int rzg2l_cru_ip_subdev_register(struct rzg2l_cru_dev *cru) +{ + struct rzg2l_cru_ip *ip = &cru->ip; + int ret; + + ip->subdev.dev = cru->dev; + v4l2_subdev_init(&ip->subdev, &rzg2l_cru_ip_subdev_ops); + v4l2_set_subdevdata(&ip->subdev, cru); + snprintf(ip->subdev.name, sizeof(ip->subdev.name), + "cru-ip-%s", dev_name(cru->dev)); + ip->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + ip->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + ip->subdev.entity.ops = &rzg2l_cru_ip_entity_ops; + + ip->pads[0].flags = MEDIA_PAD_FL_SINK; + ip->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&ip->subdev.entity, 2, ip->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(&ip->subdev); + if (ret < 0) + goto entity_cleanup; + + ret = v4l2_device_register_subdev(&cru->v4l2_dev, &ip->subdev); + if (ret < 0) + goto error_subdev; + + return 0; +error_subdev: + v4l2_subdev_cleanup(&ip->subdev); +entity_cleanup: + media_entity_cleanup(&ip->subdev.entity); + + return ret; +} + +void rzg2l_cru_ip_subdev_unregister(struct rzg2l_cru_dev *cru) +{ + struct rzg2l_cru_ip *ip = &cru->ip; + + media_entity_cleanup(&ip->subdev.entity); + v4l2_subdev_cleanup(&ip->subdev); + v4l2_device_unregister_subdev(&ip->subdev); +} diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c new file mode 100644 index 000000000000..91b57c7c2e56 --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + * + * Based on Renesas R-Car VIN + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> + +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include "rzg2l-cru.h" + +/* HW CRU Registers Definition */ + +/* CRU Control Register */ +#define CRUnCTRL 0x0 +#define CRUnCTRL_VINSEL(x) ((x) << 0) + +/* CRU Interrupt Enable Register */ +#define CRUnIE 0x4 +#define CRUnIE_EFE BIT(17) + +/* CRU Interrupt Status Register */ +#define CRUnINTS 0x8 +#define CRUnINTS_SFS BIT(16) + +/* CRU Reset Register */ +#define CRUnRST 0xc +#define CRUnRST_VRESETN BIT(0) + +/* Memory Bank Base Address (Lower) Register for CRU Image Data */ +#define AMnMBxADDRL(x) (0x100 + ((x) * 8)) + +/* Memory Bank Base Address (Higher) Register for CRU Image Data */ +#define AMnMBxADDRH(x) (0x104 + ((x) * 8)) + +/* Memory Bank Enable Register for CRU Image Data */ +#define AMnMBVALID 0x148 +#define AMnMBVALID_MBVALID(x) GENMASK(x, 0) + +/* Memory Bank Status Register for CRU Image Data */ +#define AMnMBS 0x14c +#define AMnMBS_MBSTS 0x7 + +/* AXI Master FIFO Pointer Register for CRU Image Data */ +#define AMnFIFOPNTR 0x168 +#define AMnFIFOPNTR_FIFOWPNTR GENMASK(7, 0) +#define AMnFIFOPNTR_FIFORPNTR_Y GENMASK(23, 16) + +/* AXI Master Transfer Stop Register for CRU Image Data */ +#define AMnAXISTP 0x174 +#define AMnAXISTP_AXI_STOP BIT(0) + +/* AXI Master Transfer Stop Status Register for CRU Image Data */ +#define AMnAXISTPACK 0x178 +#define AMnAXISTPACK_AXI_STOP_ACK BIT(0) + +/* CRU Image Processing Enable Register */ +#define ICnEN 0x200 +#define ICnEN_ICEN BIT(0) + +/* CRU Image Processing Main Control Register */ +#define ICnMC 0x208 +#define ICnMC_CSCTHR BIT(5) +#define ICnMC_INF_YUV8_422 (0x1e << 16) +#define ICnMC_INF_USER (0x30 << 16) +#define ICnMC_VCSEL(x) ((x) << 22) +#define ICnMC_INF_MASK GENMASK(21, 16) + +/* CRU Module Status Register */ +#define ICnMS 0x254 +#define ICnMS_IA BIT(2) + +/* CRU Data Output Mode Register */ +#define ICnDMR 0x26c +#define ICnDMR_YCMODE_UYVY (1 << 4) + +#define RZG2L_TIMEOUT_MS 100 +#define RZG2L_RETRIES 10 + +#define RZG2L_CRU_DEFAULT_FORMAT V4L2_PIX_FMT_UYVY +#define RZG2L_CRU_DEFAULT_WIDTH RZG2L_CRU_MIN_INPUT_WIDTH +#define RZG2L_CRU_DEFAULT_HEIGHT RZG2L_CRU_MIN_INPUT_HEIGHT +#define RZG2L_CRU_DEFAULT_FIELD V4L2_FIELD_NONE +#define RZG2L_CRU_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB + +struct rzg2l_cru_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +#define to_buf_list(vb2_buffer) \ + (&container_of(vb2_buffer, struct rzg2l_cru_buffer, vb)->list) + +/* ----------------------------------------------------------------------------- + * DMA operations + */ +static void rzg2l_cru_write(struct rzg2l_cru_dev *cru, u32 offset, u32 value) +{ + iowrite32(value, cru->base + offset); +} + +static u32 rzg2l_cru_read(struct rzg2l_cru_dev *cru, u32 offset) +{ + return ioread32(cru->base + offset); +} + +/* Need to hold qlock before calling */ +static void return_unused_buffers(struct rzg2l_cru_dev *cru, + enum vb2_buffer_state state) +{ + struct rzg2l_cru_buffer *buf, *node; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&cru->qlock, flags); + for (i = 0; i < cru->num_buf; i++) { + if (cru->queue_buf[i]) { + vb2_buffer_done(&cru->queue_buf[i]->vb2_buf, + state); + cru->queue_buf[i] = NULL; + } + } + + list_for_each_entry_safe(buf, node, &cru->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&cru->qlock, flags); +} + +static int rzg2l_cru_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); + + /* Make sure the image size is large enough. */ + if (*nplanes) + return sizes[0] < cru->format.sizeimage ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = cru->format.sizeimage; + + return 0; +}; + +static int rzg2l_cru_buffer_prepare(struct vb2_buffer *vb) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = cru->format.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(cru->dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void rzg2l_cru_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&cru->qlock, flags); + + list_add_tail(to_buf_list(vbuf), &cru->buf_list); + + spin_unlock_irqrestore(&cru->qlock, flags); +} + +static int rzg2l_cru_mc_validate_format(struct rzg2l_cru_dev *cru, + struct v4l2_subdev *sd, + struct media_pad *pad) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + fmt.pad = pad->index; + if (v4l2_subdev_call_state_active(sd, pad, get_fmt, &fmt)) + return -EPIPE; + + switch (fmt.format.code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + break; + default: + return -EPIPE; + } + + switch (fmt.format.field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + break; + default: + return -EPIPE; + } + + if (fmt.format.width != cru->format.width || + fmt.format.height != cru->format.height) + return -EPIPE; + + return 0; +} + +static void rzg2l_cru_set_slot_addr(struct rzg2l_cru_dev *cru, + int slot, dma_addr_t addr) +{ + /* + * The address needs to be 512 bytes aligned. Driver should never accept + * settings that do not satisfy this in the first place... + */ + if (WARN_ON((addr) & RZG2L_CRU_HW_BUFFER_MASK)) + return; + + /* Currently, we just use the buffer in 32 bits address */ + rzg2l_cru_write(cru, AMnMBxADDRL(slot), addr); + rzg2l_cru_write(cru, AMnMBxADDRH(slot), 0); +} + +/* + * Moves a buffer from the queue to the HW slot. If no buffer is + * available use the scratch buffer. The scratch buffer is never + * returned to userspace, its only function is to enable the capture + * loop to keep running. + */ +static void rzg2l_cru_fill_hw_slot(struct rzg2l_cru_dev *cru, int slot) +{ + struct vb2_v4l2_buffer *vbuf; + struct rzg2l_cru_buffer *buf; + dma_addr_t phys_addr; + + /* A already populated slot shall never be overwritten. */ + if (WARN_ON(cru->queue_buf[slot])) + return; + + dev_dbg(cru->dev, "Filling HW slot: %d\n", slot); + + if (list_empty(&cru->buf_list)) { + cru->queue_buf[slot] = NULL; + phys_addr = cru->scratch_phys; + } else { + /* Keep track of buffer we give to HW */ + buf = list_entry(cru->buf_list.next, + struct rzg2l_cru_buffer, list); + vbuf = &buf->vb; + list_del_init(to_buf_list(vbuf)); + cru->queue_buf[slot] = vbuf; + + /* Setup DMA */ + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + } + + rzg2l_cru_set_slot_addr(cru, slot, phys_addr); +} + +static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) +{ + unsigned int slot; + + /* + * Set image data memory banks. + * Currently, we will use maximum address. + */ + rzg2l_cru_write(cru, AMnMBVALID, AMnMBVALID_MBVALID(cru->num_buf - 1)); + + for (slot = 0; slot < cru->num_buf; slot++) + rzg2l_cru_fill_hw_slot(cru, slot); +} + +static void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru, bool *input_is_yuv, + struct v4l2_mbus_framefmt *ip_sd_fmt) +{ + u32 icnmc; + + switch (ip_sd_fmt->code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + icnmc = ICnMC_INF_YUV8_422; + *input_is_yuv = true; + break; + default: + *input_is_yuv = false; + icnmc = ICnMC_INF_USER; + break; + } + + icnmc |= (rzg2l_cru_read(cru, ICnMC) & ~ICnMC_INF_MASK); + + /* Set virtual channel CSI2 */ + icnmc |= ICnMC_VCSEL(cru->csi.channel); + + rzg2l_cru_write(cru, ICnMC, icnmc); +} + +static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru, + struct v4l2_mbus_framefmt *ip_sd_fmt) +{ + bool output_is_yuv = false; + bool input_is_yuv = false; + u32 icndmr; + + rzg2l_cru_csi2_setup(cru, &input_is_yuv, ip_sd_fmt); + + /* Output format */ + switch (cru->format.pixelformat) { + case V4L2_PIX_FMT_UYVY: + icndmr = ICnDMR_YCMODE_UYVY; + output_is_yuv = true; + break; + default: + dev_err(cru->dev, "Invalid pixelformat (0x%x)\n", + cru->format.pixelformat); + return -EINVAL; + } + + /* If input and output use same colorspace, do bypass mode */ + if (output_is_yuv == input_is_yuv) + rzg2l_cru_write(cru, ICnMC, + rzg2l_cru_read(cru, ICnMC) | ICnMC_CSCTHR); + else + rzg2l_cru_write(cru, ICnMC, + rzg2l_cru_read(cru, ICnMC) & (~ICnMC_CSCTHR)); + + /* Set output data format */ + rzg2l_cru_write(cru, ICnDMR, icndmr); + + return 0; +} + +void rzg2l_cru_stop_image_processing(struct rzg2l_cru_dev *cru) +{ + u32 amnfifopntr, amnfifopntr_w, amnfifopntr_r_y; + unsigned int retries = 0; + unsigned long flags; + u32 icnms; + + spin_lock_irqsave(&cru->qlock, flags); + + /* Disable and clear the interrupt */ + rzg2l_cru_write(cru, CRUnIE, 0); + rzg2l_cru_write(cru, CRUnINTS, 0x001F0F0F); + + /* Stop the operation of image conversion */ + rzg2l_cru_write(cru, ICnEN, 0); + + /* Wait for streaming to stop */ + while ((rzg2l_cru_read(cru, ICnMS) & ICnMS_IA) && retries++ < RZG2L_RETRIES) { + spin_unlock_irqrestore(&cru->qlock, flags); + msleep(RZG2L_TIMEOUT_MS); + spin_lock_irqsave(&cru->qlock, flags); + } + + icnms = rzg2l_cru_read(cru, ICnMS) & ICnMS_IA; + if (icnms) + dev_err(cru->dev, "Failed stop HW, something is seriously broken\n"); + + cru->state = RZG2L_CRU_DMA_STOPPED; + + /* Wait until the FIFO becomes empty */ + for (retries = 5; retries > 0; retries--) { + amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); + + amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; + amnfifopntr_r_y = + (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; + if (amnfifopntr_w == amnfifopntr_r_y) + break; + + usleep_range(10, 20); + } + + /* Notify that FIFO is not empty here */ + if (!retries) + dev_err(cru->dev, "Failed to empty FIFO\n"); + + /* Stop AXI bus */ + rzg2l_cru_write(cru, AMnAXISTP, AMnAXISTP_AXI_STOP); + + /* Wait until the AXI bus stop */ + for (retries = 5; retries > 0; retries--) { + if (rzg2l_cru_read(cru, AMnAXISTPACK) & + AMnAXISTPACK_AXI_STOP_ACK) + break; + + usleep_range(10, 20); + }; + + /* Notify that AXI bus can not stop here */ + if (!retries) + dev_err(cru->dev, "Failed to stop AXI bus\n"); + + /* Cancel the AXI bus stop request */ + rzg2l_cru_write(cru, AMnAXISTP, 0); + + /* Reset the CRU (AXI-master) */ + reset_control_assert(cru->aresetn); + + /* Resets the image processing module */ + rzg2l_cru_write(cru, CRUnRST, 0); + + spin_unlock_irqrestore(&cru->qlock, flags); +} + +int rzg2l_cru_start_image_processing(struct rzg2l_cru_dev *cru) +{ + struct v4l2_mbus_framefmt *fmt = rzg2l_cru_ip_get_src_fmt(cru); + unsigned long flags; + int ret; + + spin_lock_irqsave(&cru->qlock, flags); + + /* Initialize image convert */ + ret = rzg2l_cru_initialize_image_conv(cru, fmt); + if (ret) { + spin_unlock_irqrestore(&cru->qlock, flags); + return ret; + } + + /* Select a video input */ + rzg2l_cru_write(cru, CRUnCTRL, CRUnCTRL_VINSEL(0)); + + /* Cancel the software reset for image processing block */ + rzg2l_cru_write(cru, CRUnRST, CRUnRST_VRESETN); + + /* Disable and clear the interrupt before using */ + rzg2l_cru_write(cru, CRUnIE, 0); + rzg2l_cru_write(cru, CRUnINTS, 0x001f000f); + + /* Initialize the AXI master */ + rzg2l_cru_initialize_axi(cru); + + /* Enable interrupt */ + rzg2l_cru_write(cru, CRUnIE, CRUnIE_EFE); + + /* Enable image processing reception */ + rzg2l_cru_write(cru, ICnEN, ICnEN_ICEN); + + spin_unlock_irqrestore(&cru->qlock, flags); + + return 0; +} + +void rzg2l_cru_vclk_unprepare(struct rzg2l_cru_dev *cru) +{ + clk_disable_unprepare(cru->vclk); +} + +int rzg2l_cru_vclk_prepare(struct rzg2l_cru_dev *cru) +{ + return clk_prepare_enable(cru->vclk); +} + +static int rzg2l_cru_set_stream(struct rzg2l_cru_dev *cru, int on) +{ + struct media_pipeline *pipe; + struct v4l2_subdev *sd; + struct media_pad *pad; + int ret; + + pad = media_pad_remote_pad_first(&cru->pad); + if (!pad) + return -EPIPE; + + sd = media_entity_to_v4l2_subdev(pad->entity); + + if (!on) { + int stream_off_ret = 0; + + ret = v4l2_subdev_call(sd, video, s_stream, 0); + if (ret) + stream_off_ret = ret; + + ret = v4l2_subdev_call(sd, video, post_streamoff); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret && !stream_off_ret) + stream_off_ret = ret; + + video_device_pipeline_stop(&cru->vdev); + + pm_runtime_put_sync(cru->dev); + clk_disable_unprepare(cru->vclk); + + return stream_off_ret; + } + + ret = pm_runtime_resume_and_get(cru->dev); + if (ret) + return ret; + + ret = clk_prepare_enable(cru->vclk); + if (ret) + goto err_pm_put; + + ret = rzg2l_cru_mc_validate_format(cru, sd, pad); + if (ret) + goto err_vclk_disable; + + pipe = media_entity_pipeline(&sd->entity) ? : &cru->vdev.pipe; + ret = video_device_pipeline_start(&cru->vdev, pipe); + if (ret) + goto err_vclk_disable; + + ret = v4l2_subdev_call(sd, video, pre_streamon, 0); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + goto pipe_line_stop; + + ret = v4l2_subdev_call(sd, video, s_stream, 1); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + goto err_s_stream; + + return 0; + +err_s_stream: + v4l2_subdev_call(sd, video, post_streamoff); + +pipe_line_stop: + video_device_pipeline_stop(&cru->vdev); + +err_vclk_disable: + clk_disable_unprepare(cru->vclk); + +err_pm_put: + pm_runtime_put_sync(cru->dev); + + return ret; +} + +static void rzg2l_cru_stop_streaming(struct rzg2l_cru_dev *cru) +{ + cru->state = RZG2L_CRU_DMA_STOPPING; + + rzg2l_cru_set_stream(cru, 0); +} + +static irqreturn_t rzg2l_cru_irq(int irq, void *data) +{ + struct rzg2l_cru_dev *cru = data; + unsigned int handled = 0; + unsigned long flags; + u32 irq_status; + u32 amnmbs; + int slot; + + spin_lock_irqsave(&cru->qlock, flags); + + irq_status = rzg2l_cru_read(cru, CRUnINTS); + if (!irq_status) + goto done; + + handled = 1; + + rzg2l_cru_write(cru, CRUnINTS, rzg2l_cru_read(cru, CRUnINTS)); + + /* Nothing to do if capture status is 'RZG2L_CRU_DMA_STOPPED' */ + if (cru->state == RZG2L_CRU_DMA_STOPPED) { + dev_dbg(cru->dev, "IRQ while state stopped\n"); + goto done; + } + + /* Increase stop retries if capture status is 'RZG2L_CRU_DMA_STOPPING' */ + if (cru->state == RZG2L_CRU_DMA_STOPPING) { + if (irq_status & CRUnINTS_SFS) + dev_dbg(cru->dev, "IRQ while state stopping\n"); + goto done; + } + + /* Prepare for capture and update state */ + amnmbs = rzg2l_cru_read(cru, AMnMBS); + slot = amnmbs & AMnMBS_MBSTS; + + /* + * AMnMBS.MBSTS indicates the destination of Memory Bank (MB). + * Recalculate to get the current transfer complete MB. + */ + if (slot == 0) + slot = cru->num_buf - 1; + else + slot--; + + /* + * To hand buffers back in a known order to userspace start + * to capture first from slot 0. + */ + if (cru->state == RZG2L_CRU_DMA_STARTING) { + if (slot != 0) { + dev_dbg(cru->dev, "Starting sync slot: %d\n", slot); + goto done; + } + + dev_dbg(cru->dev, "Capture start synced!\n"); + cru->state = RZG2L_CRU_DMA_RUNNING; + } + + /* Capture frame */ + if (cru->queue_buf[slot]) { + cru->queue_buf[slot]->field = cru->format.field; + cru->queue_buf[slot]->sequence = cru->sequence; + cru->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&cru->queue_buf[slot]->vb2_buf, + VB2_BUF_STATE_DONE); + cru->queue_buf[slot] = NULL; + } else { + /* Scratch buffer was used, dropping frame. */ + dev_dbg(cru->dev, "Dropping frame %u\n", cru->sequence); + } + + cru->sequence++; + + /* Prepare for next frame */ + rzg2l_cru_fill_hw_slot(cru, slot); + +done: + spin_unlock_irqrestore(&cru->qlock, flags); + + return IRQ_RETVAL(handled); +} + +static int rzg2l_cru_start_streaming_vq(struct vb2_queue *vq, unsigned int count) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); + int ret; + + /* Release reset state */ + ret = reset_control_deassert(cru->aresetn); + if (ret) { + dev_err(cru->dev, "failed to deassert aresetn\n"); + return ret; + } + + ret = reset_control_deassert(cru->presetn); + if (ret) { + reset_control_assert(cru->aresetn); + dev_err(cru->dev, "failed to deassert presetn\n"); + return ret; + } + + ret = request_irq(cru->image_conv_irq, rzg2l_cru_irq, + IRQF_SHARED, KBUILD_MODNAME, cru); + if (ret) { + dev_err(cru->dev, "failed to request irq\n"); + goto assert_resets; + } + + /* Allocate scratch buffer. */ + cru->scratch = dma_alloc_coherent(cru->dev, cru->format.sizeimage, + &cru->scratch_phys, GFP_KERNEL); + if (!cru->scratch) { + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); + dev_err(cru->dev, "Failed to allocate scratch buffer\n"); + ret = -ENOMEM; + goto free_image_conv_irq; + } + + cru->sequence = 0; + + ret = rzg2l_cru_set_stream(cru, 1); + if (ret) { + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); + goto out; + } + + cru->state = RZG2L_CRU_DMA_STARTING; + dev_dbg(cru->dev, "Starting to capture\n"); + return 0; + +out: + if (ret) + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, + cru->scratch_phys); +free_image_conv_irq: + free_irq(cru->image_conv_irq, cru); + +assert_resets: + reset_control_assert(cru->presetn); + reset_control_assert(cru->aresetn); + + return ret; +} + +static void rzg2l_cru_stop_streaming_vq(struct vb2_queue *vq) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); + + rzg2l_cru_stop_streaming(cru); + + /* Free scratch buffer */ + dma_free_coherent(cru->dev, cru->format.sizeimage, + cru->scratch, cru->scratch_phys); + + free_irq(cru->image_conv_irq, cru); + reset_control_assert(cru->presetn); + + return_unused_buffers(cru, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops rzg2l_cru_qops = { + .queue_setup = rzg2l_cru_queue_setup, + .buf_prepare = rzg2l_cru_buffer_prepare, + .buf_queue = rzg2l_cru_buffer_queue, + .start_streaming = rzg2l_cru_start_streaming_vq, + .stop_streaming = rzg2l_cru_stop_streaming_vq, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru) +{ + mutex_destroy(&cru->lock); + + v4l2_device_unregister(&cru->v4l2_dev); + vb2_queue_release(&cru->queue); +} + +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru) +{ + struct vb2_queue *q = &cru->queue; + unsigned int i; + int ret; + + /* Initialize the top-level structure */ + ret = v4l2_device_register(cru->dev, &cru->v4l2_dev); + if (ret) + return ret; + + mutex_init(&cru->lock); + INIT_LIST_HEAD(&cru->buf_list); + + spin_lock_init(&cru->qlock); + + cru->state = RZG2L_CRU_DMA_STOPPED; + + for (i = 0; i < RZG2L_CRU_HW_BUFFER_MAX; i++) + cru->queue_buf[i] = NULL; + + /* buffer queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->lock = &cru->lock; + q->drv_priv = cru; + q->buf_struct_size = sizeof(struct rzg2l_cru_buffer); + q->ops = &rzg2l_cru_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 4; + q->dev = cru->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(cru->dev, "failed to initialize VB2 queue\n"); + goto error; + } + + return 0; + +error: + mutex_destroy(&cru->lock); + v4l2_device_unregister(&cru->v4l2_dev); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 stuff + */ + +static const struct v4l2_format_info rzg2l_cru_formats[] = { + { + .format = V4L2_PIX_FMT_UYVY, + .bpp[0] = 2, + }, +}; + +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_formats); i++) + if (rzg2l_cru_formats[i].format == format) + return rzg2l_cru_formats + i; + + return NULL; +} + +static u32 rzg2l_cru_format_bytesperline(struct v4l2_pix_format *pix) +{ + const struct v4l2_format_info *fmt; + + fmt = rzg2l_cru_format_from_pixel(pix->pixelformat); + + if (WARN_ON(!fmt)) + return -EINVAL; + + return pix->width * fmt->bpp[0]; +} + +static u32 rzg2l_cru_format_sizeimage(struct v4l2_pix_format *pix) +{ + return pix->bytesperline * pix->height; +} + +static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, + struct v4l2_pix_format *pix) +{ + if (!rzg2l_cru_format_from_pixel(pix->pixelformat)) + pix->pixelformat = RZG2L_CRU_DEFAULT_FORMAT; + + switch (pix->field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + break; + default: + pix->field = RZG2L_CRU_DEFAULT_FIELD; + break; + } + + /* Limit to CRU capabilities */ + v4l_bound_align_image(&pix->width, 320, RZG2L_CRU_MAX_INPUT_WIDTH, 1, + &pix->height, 240, RZG2L_CRU_MAX_INPUT_HEIGHT, 2, 0); + + pix->bytesperline = rzg2l_cru_format_bytesperline(pix); + pix->sizeimage = rzg2l_cru_format_sizeimage(pix); + + dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", + pix->width, pix->height, pix->bytesperline, pix->sizeimage); +} + +static void rzg2l_cru_try_format(struct rzg2l_cru_dev *cru, + struct v4l2_pix_format *pix) +{ + /* + * The V4L2 specification clearly documents the colorspace fields + * as being set by drivers for capture devices. Using the values + * supplied by userspace thus wouldn't comply with the API. Until + * the API is updated force fixed values. + */ + pix->colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, + pix->ycbcr_enc); + + rzg2l_cru_format_align(cru, pix); +} + +static int rzg2l_cru_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "RZG2L_CRU", sizeof(cap->card)); + + return 0; +} + +static int rzg2l_cru_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + + rzg2l_cru_try_format(cru, &f->fmt.pix); + + return 0; +} + +static int rzg2l_cru_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + + if (vb2_is_busy(&cru->queue)) + return -EBUSY; + + rzg2l_cru_try_format(cru, &f->fmt.pix); + + cru->format = f->fmt.pix; + + return 0; +} + +static int rzg2l_cru_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + + f->fmt.pix = cru->format; + + return 0; +} + +static int rzg2l_cru_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(rzg2l_cru_formats)) + return -EINVAL; + + f->pixelformat = rzg2l_cru_formats[f->index].format; + + return 0; +} + +static const struct v4l2_ioctl_ops rzg2l_cru_ioctl_ops = { + .vidioc_querycap = rzg2l_cru_querycap, + .vidioc_try_fmt_vid_cap = rzg2l_cru_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = rzg2l_cru_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = rzg2l_cru_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = rzg2l_cru_enum_fmt_vid_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Media controller file operations + */ + +static int rzg2l_cru_open(struct file *file) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + int ret; + + ret = mutex_lock_interruptible(&cru->lock); + if (ret) + return ret; + + file->private_data = cru; + ret = v4l2_fh_open(file); + if (ret) + goto err_unlock; + + mutex_unlock(&cru->lock); + + return 0; + +err_unlock: + mutex_unlock(&cru->lock); + + return ret; +} + +static int rzg2l_cru_release(struct file *file) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + int ret; + + mutex_lock(&cru->lock); + + /* the release helper will cleanup any on-going streaming. */ + ret = _vb2_fop_release(file, NULL); + + mutex_unlock(&cru->lock); + + return ret; +} + +static const struct v4l2_file_operations rzg2l_cru_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = rzg2l_cru_open, + .release = rzg2l_cru_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +static void rzg2l_cru_v4l2_init(struct rzg2l_cru_dev *cru) +{ + struct video_device *vdev = &cru->vdev; + + vdev->v4l2_dev = &cru->v4l2_dev; + vdev->queue = &cru->queue; + snprintf(vdev->name, sizeof(vdev->name), "CRU output"); + vdev->release = video_device_release_empty; + vdev->lock = &cru->lock; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + vdev->device_caps |= V4L2_CAP_IO_MC; + vdev->fops = &rzg2l_cru_fops; + vdev->ioctl_ops = &rzg2l_cru_ioctl_ops; + + /* Set a default format */ + cru->format.pixelformat = RZG2L_CRU_DEFAULT_FORMAT; + cru->format.width = RZG2L_CRU_DEFAULT_WIDTH; + cru->format.height = RZG2L_CRU_DEFAULT_HEIGHT; + cru->format.field = RZG2L_CRU_DEFAULT_FIELD; + cru->format.colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; + rzg2l_cru_format_align(cru, &cru->format); +} + +void rzg2l_cru_video_unregister(struct rzg2l_cru_dev *cru) +{ + media_device_unregister(&cru->mdev); + video_unregister_device(&cru->vdev); +} + +int rzg2l_cru_video_register(struct rzg2l_cru_dev *cru) +{ + struct video_device *vdev = &cru->vdev; + int ret; + + if (video_is_registered(&cru->vdev)) { + struct media_entity *entity; + + entity = &cru->vdev.entity; + if (!entity->graph_obj.mdev) + entity->graph_obj.mdev = &cru->mdev; + return 0; + } + + rzg2l_cru_v4l2_init(cru); + video_set_drvdata(vdev, cru); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(cru->dev, "Failed to register video device\n"); + return ret; + } + + ret = media_device_register(&cru->mdev); + if (ret) { + video_unregister_device(&cru->vdev); + return ret; + } + + return 0; +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c index d8731ebbf479..3482f7d707b7 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c @@ -715,7 +715,7 @@ static void rkisp1_aec_config_v12(struct rkisp1_params *params, u32 exp_ctrl; u32 block_hsize, block_vsize; u32 wnd_num_idx = 1; - const u32 ae_wnd_num[] = { 5, 9, 15, 15 }; + static const u32 ae_wnd_num[] = { 5, 9, 15, 15 }; /* avoid to override the old enable value */ exp_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_EXP_CTRL); @@ -822,7 +822,7 @@ static void rkisp1_hst_config_v12(struct rkisp1_params *params, u32 block_hsize, block_vsize; u32 wnd_num_idx, hist_weight_num, hist_ctrl, value; u8 weight15x15[RKISP1_CIF_ISP_HIST_WEIGHT_REG_SIZE_V12]; - const u32 hist_wnd_num[] = { 5, 9, 15, 15 }; + static const u32 hist_wnd_num[] = { 5, 9, 15, 15 }; /* now we just support 9x9 window */ wnd_num_idx = 1; diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-core.c b/drivers/media/platform/samsung/exynos4-is/fimc-core.c index 91cc8d58a663..1791100b6935 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-core.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-core.c @@ -1173,7 +1173,7 @@ int __init fimc_register_driver(void) return platform_driver_register(&fimc_driver); } -void __exit fimc_unregister_driver(void) +void fimc_unregister_driver(void) { platform_driver_unregister(&fimc_driver); } diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c index 52b43ea04030..98a60f01129d 100644 --- a/drivers/media/platform/samsung/exynos4-is/media-dev.c +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c @@ -1380,9 +1380,7 @@ static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, /* Find platform data for this sensor subdev */ for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++) - if (fmd->sensor[i].asd && - fmd->sensor[i].asd->match.fwnode == - of_fwnode_handle(subdev->dev->of_node)) + if (fmd->sensor[i].asd == asd) si = &fmd->sensor[i]; if (si == NULL) @@ -1473,9 +1471,7 @@ static int fimc_md_probe(struct platform_device *pdev) pinctrl = devm_pinctrl_get(dev); if (IS_ERR(pinctrl)) { - ret = PTR_ERR(pinctrl); - if (ret != EPROBE_DEFER) - dev_err(dev, "Failed to get pinctrl: %d\n", ret); + ret = dev_err_probe(dev, PTR_ERR(pinctrl), "Failed to get pinctrl\n"); goto err_clk; } @@ -1586,7 +1582,11 @@ static int __init fimc_md_init(void) if (ret) return ret; - return platform_driver_register(&fimc_md_driver); + ret = platform_driver_register(&fimc_md_driver); + if (ret) + fimc_unregister_driver(); + + return ret; } static void __exit fimc_md_exit(void) diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c index fca5c6405eec..f3e4cdac1ef3 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c @@ -36,7 +36,7 @@ #define S5P_MFC_ENC_NAME "s5p-mfc-enc" int mfc_debug_level; -module_param_named(debug, mfc_debug_level, int, S_IRUGO | S_IWUSR); +module_param_named(debug, mfc_debug_level, int, 0644); MODULE_PARM_DESC(debug, "Debug level - higher value produces more verbose messages"); static char *mfc_mem_size; @@ -148,11 +148,13 @@ static void s5p_mfc_watchdog(struct timer_list *t) if (test_bit(0, &dev->hw_lock)) atomic_inc(&dev->watchdog_cnt); if (atomic_read(&dev->watchdog_cnt) >= MFC_WATCHDOG_CNT) { - /* This means that hw is busy and no interrupts were + /* + * This means that hw is busy and no interrupts were * generated by hw for the Nth time of running this * watchdog timer. This usually means a serious hw * error. Now it is time to kill all instances and - * reset the MFC. */ + * reset the MFC. + */ mfc_err("Time out during waiting for HW\n"); schedule_work(&dev->watchdog_work); } @@ -172,8 +174,10 @@ static void s5p_mfc_watchdog_worker(struct work_struct *work) dev = container_of(work, struct s5p_mfc_dev, watchdog_work); mfc_err("Driver timeout error handling\n"); - /* Lock the mutex that protects open and release. - * This is necessary as they may load and unload firmware. */ + /* + * Lock the mutex that protects open and release. + * This is necessary as they may load and unload firmware. + */ mutex_locked = mutex_trylock(&dev->mfc_mutex); if (!mutex_locked) mfc_err("Error: some instance may be closing/opening\n"); @@ -197,8 +201,10 @@ static void s5p_mfc_watchdog_worker(struct work_struct *work) /* De-init MFC */ s5p_mfc_deinit_hw(dev); - /* Double check if there is at least one instance running. - * If no instance is in memory than no firmware should be present */ + /* + * Double check if there is at least one instance running. + * If no instance is in memory than no firmware should be present + */ if (dev->num_inst > 0) { ret = s5p_mfc_load_firmware(dev); if (ret) { @@ -260,8 +266,10 @@ static void s5p_mfc_handle_frame_copy_time(struct s5p_mfc_ctx *ctx) return; dec_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dec_y_adr, dev); - /* Copy timestamp / timecode from decoded src to dst and set - appropriate flags. */ + /* + * Copy timestamp / timecode from decoded src to dst and set + * appropriate flags. + */ src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); list_for_each_entry(dst_buf, &ctx->dst_queue, list) { u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0); @@ -289,8 +297,10 @@ static void s5p_mfc_handle_frame_copy_time(struct s5p_mfc_ctx *ctx) V4L2_BUF_FLAG_BFRAME; break; default: - /* Don't know how to handle - S5P_FIMV_DECODE_FRAME_OTHER_FRAME. */ + /* + * Don't know how to handle + * S5P_FIMV_DECODE_FRAME_OTHER_FRAME. + */ mfc_debug(2, "Unexpected frame type: %d\n", frame_type); } @@ -322,8 +332,10 @@ static void s5p_mfc_handle_frame_new(struct s5p_mfc_ctx *ctx, unsigned int err) return; } ctx->sequence++; - /* The MFC returns address of the buffer, now we have to - * check which vb2_buffer does it correspond to */ + /* + * The MFC returns address of the buffer, now we have to + * check which vb2_buffer does it correspond to + */ list_for_each_entry(dst_buf, &ctx->dst_queue, list) { u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0); @@ -476,8 +488,10 @@ static void s5p_mfc_handle_error(struct s5p_mfc_dev *dev, case MFCINST_FINISHING: case MFCINST_FINISHED: case MFCINST_RUNNING: - /* It is highly probable that an error occurred - * while decoding a frame */ + /* + * It is highly probable that an error occurred + * while decoding a frame + */ clear_work_bit(ctx); ctx->state = MFCINST_ERROR; /* Mark all dst buffers as having an error */ @@ -535,6 +549,7 @@ static void s5p_mfc_handle_seq_done(struct s5p_mfc_ctx *ctx, ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC) && !list_empty(&ctx->src_queue)) { struct s5p_mfc_buf *src_buf; + src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); if (s5p_mfc_hw_call(dev->mfc_ops, get_consumed_stream, @@ -951,7 +966,7 @@ static int s5p_mfc_release(struct file *file) /* * If instance was initialised and not yet freed, * return instance and free resources - */ + */ if (ctx->state != MFCINST_FREE && ctx->state != MFCINST_INIT) { mfc_debug(2, "Has to free instance\n"); s5p_mfc_close_mfc_inst(dev, ctx); @@ -1047,10 +1062,10 @@ static int s5p_mfc_mmap(struct file *file, struct vm_area_struct *vma) int ret; if (offset < DST_QUEUE_OFF_BASE) { - mfc_debug(2, "mmaping source\n"); + mfc_debug(2, "mmapping source\n"); ret = vb2_mmap(&ctx->vq_src, vma); } else { /* capture */ - mfc_debug(2, "mmaping destination\n"); + mfc_debug(2, "mmapping destination\n"); vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT); ret = vb2_mmap(&ctx->vq_dst, vma); } @@ -1149,7 +1164,6 @@ static int s5p_mfc_configure_2port_memory(struct s5p_mfc_dev *mfc_dev) bank2_virt = dma_alloc_coherent(mfc_dev->mem_dev[BANK_R_CTX], align_size, &bank2_dma_addr, GFP_KERNEL); if (!bank2_virt) { - mfc_err("Allocating bank2 base failed\n"); s5p_mfc_release_firmware(mfc_dev); device_unregister(mfc_dev->mem_dev[BANK_R_CTX]); device_unregister(mfc_dev->mem_dev[BANK_L_CTX]); @@ -1318,7 +1332,7 @@ static int s5p_mfc_probe(struct platform_device *pdev) /* * Load fails if fs isn't mounted. Try loading anyway. - * _open() will load it, it it fails now. Ignore failure. + * _open() will load it, it fails now. Ignore failure. */ s5p_mfc_load_firmware(dev); @@ -1429,7 +1443,7 @@ static int s5p_mfc_remove(struct platform_device *pdev) * Clear ctx dev pointer to avoid races between s5p_mfc_remove() * and s5p_mfc_release() and s5p_mfc_release() accessing ctx->dev * after s5p_mfc_remove() is run during unbind. - */ + */ mutex_lock(&dev->mfc_mutex); for (i = 0; i < MFC_NUM_CONTEXTS; i++) { ctx = dev->ctx[i]; @@ -1576,8 +1590,18 @@ static struct s5p_mfc_variant mfc_drvdata_v7 = { .port_num = MFC_NUM_PORTS_V7, .buf_size = &buf_size_v7, .fw_name[0] = "s5p-mfc-v7.fw", - .clk_names = {"mfc", "sclk_mfc"}, - .num_clocks = 2, + .clk_names = {"mfc"}, + .num_clocks = 1, +}; + +static struct s5p_mfc_variant mfc_drvdata_v7_3250 = { + .version = MFC_VERSION_V7, + .version_bit = MFC_V7_BIT, + .port_num = MFC_NUM_PORTS_V7, + .buf_size = &buf_size_v7, + .fw_name[0] = "s5p-mfc-v7.fw", + .clk_names = {"mfc", "sclk_mfc"}, + .num_clocks = 2, }; static struct s5p_mfc_buf_size_v6 mfc_buf_size_v8 = { @@ -1648,6 +1672,9 @@ static const struct of_device_id exynos_mfc_match[] = { .compatible = "samsung,mfc-v7", .data = &mfc_drvdata_v7, }, { + .compatible = "samsung,exynos3250-mfc", + .data = &mfc_drvdata_v7_3250, + }, { .compatible = "samsung,mfc-v8", .data = &mfc_drvdata_v8, }, { diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c index 72d70984e99a..6d3c92045c05 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c @@ -468,8 +468,10 @@ void s5p_mfc_close_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx) s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); /* Wait until instance is returned or timeout occurred */ if (s5p_mfc_wait_for_done_ctx(ctx, - S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET, 0)) + S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET, 0)){ + clear_work_bit_irqsave(ctx); mfc_err("Err returning instance\n"); + } /* Free resources */ s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, ctx); diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c index b65e506665af..f62703cebb77 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c @@ -1218,6 +1218,7 @@ static int enc_post_frame_start(struct s5p_mfc_ctx *ctx) unsigned long mb_y_addr, mb_c_addr; int slice_type; unsigned int strm_size; + bool src_ready; slice_type = s5p_mfc_hw_call(dev->mfc_ops, get_enc_slice_type, dev); strm_size = s5p_mfc_hw_call(dev->mfc_ops, get_enc_strm_size, dev); @@ -1257,7 +1258,8 @@ static int enc_post_frame_start(struct s5p_mfc_ctx *ctx) } } } - if ((ctx->src_queue_cnt > 0) && (ctx->state == MFCINST_RUNNING)) { + if (ctx->src_queue_cnt > 0 && (ctx->state == MFCINST_RUNNING || + ctx->state == MFCINST_FINISHING)) { mb_entry = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); if (mb_entry->flags & MFC_BUF_FLAG_USED) { @@ -1288,7 +1290,13 @@ static int enc_post_frame_start(struct s5p_mfc_ctx *ctx) vb2_set_plane_payload(&mb_entry->b->vb2_buf, 0, strm_size); vb2_buffer_done(&mb_entry->b->vb2_buf, VB2_BUF_STATE_DONE); } - if ((ctx->src_queue_cnt == 0) || (ctx->dst_queue_cnt == 0)) + + src_ready = true; + if (ctx->state == MFCINST_RUNNING && ctx->src_queue_cnt == 0) + src_ready = false; + if (ctx->state == MFCINST_FINISHING && ctx->ref_queue_cnt == 0) + src_ready = false; + if (!src_ready || ctx->dst_queue_cnt == 0) clear_work_bit(ctx); return 0; diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c index 8227004f6746..c0df5ac9fcff 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c @@ -1060,7 +1060,7 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) } /* aspect ratio VUI */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 5); reg |= ((p_h264->vui_sar & 0x1) << 5); writel(reg, mfc_regs->e_h264_options); @@ -1083,7 +1083,7 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) /* intra picture period for H.264 open GOP */ /* control */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 4); reg |= ((p_h264->open_gop & 0x1) << 4); writel(reg, mfc_regs->e_h264_options); @@ -1097,23 +1097,23 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) } /* 'WEIGHTED_BI_PREDICTION' for B is disable */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x3 << 9); writel(reg, mfc_regs->e_h264_options); /* 'CONSTRAINED_INTRA_PRED_ENABLE' is disable */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 14); writel(reg, mfc_regs->e_h264_options); /* ASO */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 6); reg |= ((p_h264->aso & 0x1) << 6); writel(reg, mfc_regs->e_h264_options); /* hier qp enable */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 8); reg |= ((p_h264->open_gop & 0x1) << 8); writel(reg, mfc_regs->e_h264_options); @@ -1134,7 +1134,7 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) writel(reg, mfc_regs->e_h264_num_t_layer); /* frame packing SEI generation */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 25); reg |= ((p_h264->sei_frame_packing & 0x1) << 25); writel(reg, mfc_regs->e_h264_options); diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c index cefe6b7bfdc4..c38b62d4f1ae 100644 --- a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c @@ -24,16 +24,18 @@ #include <linux/module.h> #include <linux/of_gpio.h> #include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pinctrl/pinctrl.h> #include <linux/platform_device.h> -#include <linux/usb.h> #include <linux/slab.h> #include <linux/time.h> +#include <linux/usb.h> #include <linux/wait.h> -#include <linux/pinctrl/pinctrl.h> -#include "c8sectpfe-core.h" #include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" #include "c8sectpfe-debugfs.h" + #include <media/dmxdev.h> #include <media/dvb_demux.h> #include <media/dvb_frontend.h> @@ -925,6 +927,7 @@ static int configure_channels(struct c8sectpfei *fei) if (ret) { dev_err(fei->dev, "configure_memdma_and_inputblock failed\n"); + of_node_put(child); goto err_unmap; } index++; diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 37458d4d9564..ad8e9742e1ae 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -1946,12 +1946,9 @@ static int dcmi_probe(struct platform_device *pdev) return -ENOMEM; dcmi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); - if (IS_ERR(dcmi->rstc)) { - if (PTR_ERR(dcmi->rstc) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Could not get reset control\n"); - - return PTR_ERR(dcmi->rstc); - } + if (IS_ERR(dcmi->rstc)) + return dev_err_probe(&pdev->dev, PTR_ERR(dcmi->rstc), + "Could not get reset control\n"); /* Get bus characteristics from devicetree */ np = of_graph_get_next_endpoint(np, NULL); @@ -1997,26 +1994,18 @@ static int dcmi_probe(struct platform_device *pdev) } dcmi->regs = devm_ioremap_resource(&pdev->dev, dcmi->res); - if (IS_ERR(dcmi->regs)) { - dev_err(&pdev->dev, "Could not map registers\n"); + if (IS_ERR(dcmi->regs)) return PTR_ERR(dcmi->regs); - } mclk = devm_clk_get(&pdev->dev, "mclk"); - if (IS_ERR(mclk)) { - if (PTR_ERR(mclk) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Unable to get mclk\n"); - return PTR_ERR(mclk); - } + if (IS_ERR(mclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(mclk), + "Unable to get mclk\n"); chan = dma_request_chan(&pdev->dev, "tx"); - if (IS_ERR(chan)) { - ret = PTR_ERR(chan); - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, - "Failed to request DMA channel: %d\n", ret); - return ret; - } + if (IS_ERR(chan)) + return dev_err_probe(&pdev->dev, PTR_ERR(chan), + "Failed to request DMA channel\n"); dcmi->dma_max_burst = UINT_MAX; ret = dma_get_slave_caps(chan, &caps); diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile index e7e315347804..87e7a715140a 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/Makefile +++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -sun6i-csi-y += sun6i_video.o sun6i_csi.o +sun6i-csi-y += sun6i_csi.o sun6i_csi_bridge.o sun6i_csi_capture.o obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c index 8b99c17e8403..e3e6650181c8 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c @@ -1,18 +1,14 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ #include <linux/clk.h> -#include <linux/delay.h> -#include <linux/dma-mapping.h> #include <linux/err.h> -#include <linux/fs.h> #include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/ioctl.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> @@ -20,561 +16,56 @@ #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/reset.h> -#include <linux/sched.h> -#include <linux/sizes.h> -#include <linux/slab.h> +#include <media/v4l2-device.h> #include <media/v4l2-mc.h> #include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" #include "sun6i_csi_reg.h" -/* Helpers */ +/* ISP */ -/* TODO add 10&12 bit YUV, RGB support */ -bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, - u32 pixformat, u32 mbus_code) +int sun6i_csi_isp_complete(struct sun6i_csi_device *csi_dev, + struct v4l2_device *v4l2_dev) { - struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; - - /* - * Some video receivers have the ability to be compatible with - * 8bit and 16bit bus width. - * Identify the media bus format from device tree. - */ - if ((v4l2->v4l2_ep.bus_type == V4L2_MBUS_PARALLEL - || v4l2->v4l2_ep.bus_type == V4L2_MBUS_BT656) - && v4l2->v4l2_ep.bus.parallel.bus_width == 16) { - switch (pixformat) { - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_YUV422P: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_1X16: - case MEDIA_BUS_FMT_VYUY8_1X16: - case MEDIA_BUS_FMT_YUYV8_1X16: - case MEDIA_BUS_FMT_YVYU8_1X16: - return true; - default: - dev_dbg(csi_dev->dev, - "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - default: - dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", - pixformat); - break; - } - return false; - } + if (csi_dev->v4l2_dev && csi_dev->v4l2_dev != v4l2_dev) + return -EINVAL; - switch (pixformat) { - case V4L2_PIX_FMT_SBGGR8: - return (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8); - case V4L2_PIX_FMT_SGBRG8: - return (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8); - case V4L2_PIX_FMT_SGRBG8: - return (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8); - case V4L2_PIX_FMT_SRGGB8: - return (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8); - case V4L2_PIX_FMT_SBGGR10: - return (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10); - case V4L2_PIX_FMT_SGBRG10: - return (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10); - case V4L2_PIX_FMT_SGRBG10: - return (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10); - case V4L2_PIX_FMT_SRGGB10: - return (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10); - case V4L2_PIX_FMT_SBGGR12: - return (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12); - case V4L2_PIX_FMT_SGBRG12: - return (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12); - case V4L2_PIX_FMT_SGRBG12: - return (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12); - case V4L2_PIX_FMT_SRGGB12: - return (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12); - - case V4L2_PIX_FMT_YUYV: - return (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8); - case V4L2_PIX_FMT_YVYU: - return (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8); - case V4L2_PIX_FMT_UYVY: - return (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8); - case V4L2_PIX_FMT_VYUY: - return (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8); - - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_YUV422P: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_VYUY8_2X8: - case MEDIA_BUS_FMT_YUYV8_2X8: - case MEDIA_BUS_FMT_YVYU8_2X8: - return true; - default: - dev_dbg(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - - case V4L2_PIX_FMT_RGB565: - return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_LE); - case V4L2_PIX_FMT_RGB565X: - return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_BE); - - case V4L2_PIX_FMT_JPEG: - return (mbus_code == MEDIA_BUS_FMT_JPEG_1X8); - - default: - dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", - pixformat); - break; - } + csi_dev->v4l2_dev = v4l2_dev; + csi_dev->media_dev = v4l2_dev->mdev; - return false; + return sun6i_csi_capture_setup(csi_dev); } -int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable) +static int sun6i_csi_isp_detect(struct sun6i_csi_device *csi_dev) { struct device *dev = csi_dev->dev; - struct regmap *regmap = csi_dev->regmap; - int ret; - - if (!enable) { - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); - pm_runtime_put(dev); + struct fwnode_handle *handle; + /* + * ISP is not available if not connected via fwnode graph. + * This will also check that the remote parent node is available. + */ + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + SUN6I_CSI_PORT_ISP, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!handle) return 0; - } - - ret = pm_runtime_resume_and_get(dev); - if (ret < 0) - return ret; - - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN); - - return 0; -} - -static enum csi_input_fmt get_csi_input_format(struct sun6i_csi_device *csi_dev, - u32 mbus_code, u32 pixformat) -{ - /* non-YUV */ - if ((mbus_code & 0xF000) != 0x2000) - return CSI_INPUT_FORMAT_RAW; - - switch (pixformat) { - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - return CSI_INPUT_FORMAT_RAW; - default: - break; - } - /* not support YUV420 input format yet */ - dev_dbg(csi_dev->dev, "Select YUV422 as default input format of CSI.\n"); - return CSI_INPUT_FORMAT_YUV422; -} - -static enum csi_output_fmt -get_csi_output_format(struct sun6i_csi_device *csi_dev, u32 pixformat, - u32 field) -{ - bool buf_interlaced = false; - - if (field == V4L2_FIELD_INTERLACED - || field == V4L2_FIELD_INTERLACED_TB - || field == V4L2_FIELD_INTERLACED_BT) - buf_interlaced = true; - - switch (pixformat) { - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SGBRG8: - case V4L2_PIX_FMT_SGRBG8: - case V4L2_PIX_FMT_SRGGB8: - return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; - case V4L2_PIX_FMT_SBGGR10: - case V4L2_PIX_FMT_SGBRG10: - case V4L2_PIX_FMT_SGRBG10: - case V4L2_PIX_FMT_SRGGB10: - return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10; - case V4L2_PIX_FMT_SBGGR12: - case V4L2_PIX_FMT_SGBRG12: - case V4L2_PIX_FMT_SGRBG12: - case V4L2_PIX_FMT_SRGGB12: - return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12; - - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; - - case V4L2_PIX_FMT_NV12_16L16: - return buf_interlaced ? CSI_FRAME_MB_YUV420 : - CSI_FIELD_MB_YUV420; - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 : - CSI_FIELD_UV_CB_YUV420; - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 : - CSI_FIELD_PLANAR_YUV420; - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 : - CSI_FIELD_UV_CB_YUV422; - case V4L2_PIX_FMT_YUV422P: - return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 : - CSI_FIELD_PLANAR_YUV422; - - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_RGB565X: - return buf_interlaced ? CSI_FRAME_RGB565 : CSI_FIELD_RGB565; - - case V4L2_PIX_FMT_JPEG: - return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; - - default: - dev_warn(csi_dev->dev, "Unsupported pixformat: 0x%x\n", pixformat); - break; - } + fwnode_handle_put(handle); - return CSI_FIELD_RAW_8; -} - -static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_device *csi_dev, - u32 mbus_code, u32 pixformat) -{ - /* Input sequence does not apply to non-YUV formats */ - if ((mbus_code & 0xF000) != 0x2000) + if (!IS_ENABLED(CONFIG_VIDEO_SUN6I_ISP)) { + dev_warn(dev, + "ISP link is detected but not enabled in kernel config!"); return 0; - - switch (pixformat) { - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YUV422P: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_UYVY8_1X16: - return CSI_INPUT_SEQ_UYVY; - case MEDIA_BUS_FMT_VYUY8_2X8: - case MEDIA_BUS_FMT_VYUY8_1X16: - return CSI_INPUT_SEQ_VYUY; - case MEDIA_BUS_FMT_YUYV8_2X8: - case MEDIA_BUS_FMT_YUYV8_1X16: - return CSI_INPUT_SEQ_YUYV; - case MEDIA_BUS_FMT_YVYU8_1X16: - case MEDIA_BUS_FMT_YVYU8_2X8: - return CSI_INPUT_SEQ_YVYU; - default: - dev_warn(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YVU420: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_UYVY8_1X16: - return CSI_INPUT_SEQ_VYUY; - case MEDIA_BUS_FMT_VYUY8_2X8: - case MEDIA_BUS_FMT_VYUY8_1X16: - return CSI_INPUT_SEQ_UYVY; - case MEDIA_BUS_FMT_YUYV8_2X8: - case MEDIA_BUS_FMT_YUYV8_1X16: - return CSI_INPUT_SEQ_YVYU; - case MEDIA_BUS_FMT_YVYU8_1X16: - case MEDIA_BUS_FMT_YVYU8_2X8: - return CSI_INPUT_SEQ_YUYV; - default: - dev_warn(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - - case V4L2_PIX_FMT_YUYV: - return CSI_INPUT_SEQ_YUYV; - - default: - dev_warn(csi_dev->dev, "Unsupported pixformat: 0x%x, defaulting to YUYV\n", - pixformat); - break; - } - - return CSI_INPUT_SEQ_YUYV; -} - -static void sun6i_csi_setup_bus(struct sun6i_csi_device *csi_dev) -{ - struct v4l2_fwnode_endpoint *endpoint = &csi_dev->v4l2.v4l2_ep; - struct sun6i_csi_config *config = &csi_dev->config; - unsigned char bus_width; - u32 flags; - u32 cfg; - bool input_interlaced = false; - - if (config->field == V4L2_FIELD_INTERLACED - || config->field == V4L2_FIELD_INTERLACED_TB - || config->field == V4L2_FIELD_INTERLACED_BT) - input_interlaced = true; - - bus_width = endpoint->bus.parallel.bus_width; - - regmap_read(csi_dev->regmap, CSI_IF_CFG_REG, &cfg); - - cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK | - CSI_IF_CFG_IF_DATA_WIDTH_MASK | - CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK | - CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK | - CSI_IF_CFG_SRC_TYPE_MASK); - - if (input_interlaced) - cfg |= CSI_IF_CFG_SRC_TYPE_INTERLACED; - else - cfg |= CSI_IF_CFG_SRC_TYPE_PROGRESSED; - - switch (endpoint->bus_type) { - case V4L2_MBUS_PARALLEL: - cfg |= CSI_IF_CFG_MIPI_IF_CSI; - - flags = endpoint->bus.parallel.flags; - - cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT : - CSI_IF_CFG_CSI_IF_YUV422_INTLV; - - if (flags & V4L2_MBUS_FIELD_EVEN_LOW) - cfg |= CSI_IF_CFG_FIELD_POSITIVE; - - if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) - cfg |= CSI_IF_CFG_VREF_POL_POSITIVE; - if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) - cfg |= CSI_IF_CFG_HREF_POL_POSITIVE; - - if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) - cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; - break; - case V4L2_MBUS_BT656: - cfg |= CSI_IF_CFG_MIPI_IF_CSI; - - flags = endpoint->bus.parallel.flags; - - cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 : - CSI_IF_CFG_CSI_IF_BT656; - - if (flags & V4L2_MBUS_FIELD_EVEN_LOW) - cfg |= CSI_IF_CFG_FIELD_POSITIVE; - - if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) - cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; - break; - default: - dev_warn(csi_dev->dev, "Unsupported bus type: %d\n", - endpoint->bus_type); - break; - } - - switch (bus_width) { - case 8: - cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT; - break; - case 10: - cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT; - break; - case 12: - cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT; - break; - case 16: /* No need to configure DATA_WIDTH for 16bit */ - break; - default: - dev_warn(csi_dev->dev, "Unsupported bus width: %u\n", bus_width); - break; - } - - regmap_write(csi_dev->regmap, CSI_IF_CFG_REG, cfg); -} - -static void sun6i_csi_set_format(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_csi_config *config = &csi_dev->config; - u32 cfg; - u32 val; - - regmap_read(csi_dev->regmap, CSI_CH_CFG_REG, &cfg); - - cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK | - CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN | - CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK | - CSI_CH_CFG_INPUT_SEQ_MASK); - - val = get_csi_input_format(csi_dev, config->code, - config->pixelformat); - cfg |= CSI_CH_CFG_INPUT_FMT(val); - - val = get_csi_output_format(csi_dev, config->pixelformat, - config->field); - cfg |= CSI_CH_CFG_OUTPUT_FMT(val); - - val = get_csi_input_seq(csi_dev, config->code, - config->pixelformat); - cfg |= CSI_CH_CFG_INPUT_SEQ(val); - - if (config->field == V4L2_FIELD_TOP) - cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0; - else if (config->field == V4L2_FIELD_BOTTOM) - cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1; - else - cfg |= CSI_CH_CFG_FIELD_SEL_BOTH; - - regmap_write(csi_dev->regmap, CSI_CH_CFG_REG, cfg); -} - -static void sun6i_csi_set_window(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_csi_config *config = &csi_dev->config; - u32 bytesperline_y; - u32 bytesperline_c; - int *planar_offset = csi_dev->planar_offset; - u32 width = config->width; - u32 height = config->height; - u32 hor_len = width; - - switch (config->pixelformat) { - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - dev_dbg(csi_dev->dev, - "Horizontal length should be 2 times of width for packed YUV formats!\n"); - hor_len = width * 2; - break; - default: - break; - } - - regmap_write(csi_dev->regmap, CSI_CH_HSIZE_REG, - CSI_CH_HSIZE_HOR_LEN(hor_len) | - CSI_CH_HSIZE_HOR_START(0)); - regmap_write(csi_dev->regmap, CSI_CH_VSIZE_REG, - CSI_CH_VSIZE_VER_LEN(height) | - CSI_CH_VSIZE_VER_START(0)); - - planar_offset[0] = 0; - switch (config->pixelformat) { - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - bytesperline_y = width; - bytesperline_c = width; - planar_offset[1] = bytesperline_y * height; - planar_offset[2] = -1; - break; - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - bytesperline_y = width; - bytesperline_c = width / 2; - planar_offset[1] = bytesperline_y * height; - planar_offset[2] = planar_offset[1] + - bytesperline_c * height / 2; - break; - case V4L2_PIX_FMT_YUV422P: - bytesperline_y = width; - bytesperline_c = width / 2; - planar_offset[1] = bytesperline_y * height; - planar_offset[2] = planar_offset[1] + - bytesperline_c * height; - break; - default: /* raw */ - dev_dbg(csi_dev->dev, - "Calculating pixelformat(0x%x)'s bytesperline as a packed format\n", - config->pixelformat); - bytesperline_y = (sun6i_csi_get_bpp(config->pixelformat) * - config->width) / 8; - bytesperline_c = 0; - planar_offset[1] = -1; - planar_offset[2] = -1; - break; } - regmap_write(csi_dev->regmap, CSI_CH_BUF_LEN_REG, - CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) | - CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y)); -} - -int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_config *config) -{ - if (!config) - return -EINVAL; - - memcpy(&csi_dev->config, config, sizeof(csi_dev->config)); - - sun6i_csi_setup_bus(csi_dev); - sun6i_csi_set_format(csi_dev); - sun6i_csi_set_window(csi_dev); + csi_dev->isp_available = true; return 0; } -void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, - dma_addr_t addr) -{ - regmap_write(csi_dev->regmap, CSI_CH_F0_BUFA_REG, - (addr + csi_dev->planar_offset[0]) >> 2); - if (csi_dev->planar_offset[1] != -1) - regmap_write(csi_dev->regmap, CSI_CH_F1_BUFA_REG, - (addr + csi_dev->planar_offset[1]) >> 2); - if (csi_dev->planar_offset[2] != -1) - regmap_write(csi_dev->regmap, CSI_CH_F2_BUFA_REG, - (addr + csi_dev->planar_offset[2]) >> 2); -} - -void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, bool enable) -{ - struct regmap *regmap = csi_dev->regmap; - - if (!enable) { - regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0); - regmap_write(regmap, CSI_CH_INT_EN_REG, 0); - return; - } - - regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF); - regmap_write(regmap, CSI_CH_INT_EN_REG, - CSI_CH_INT_EN_HB_OF_INT_EN | - CSI_CH_INT_EN_FIFO2_OF_INT_EN | - CSI_CH_INT_EN_FIFO1_OF_INT_EN | - CSI_CH_INT_EN_FIFO0_OF_INT_EN | - CSI_CH_INT_EN_FD_INT_EN | - CSI_CH_INT_EN_CD_INT_EN); - - regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, - CSI_CAP_CH0_VCAP_ON); -} - /* Media */ static const struct media_device_ops sun6i_csi_media_ops = { @@ -583,103 +74,11 @@ static const struct media_device_ops sun6i_csi_media_ops = { /* V4L2 */ -static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev, - struct media_entity *entity, - struct fwnode_handle *fwnode) -{ - struct media_entity *sink; - struct media_pad *sink_pad; - int src_pad_index; - int ret; - - ret = media_entity_get_fwnode_pad(entity, fwnode, MEDIA_PAD_FL_SOURCE); - if (ret < 0) { - dev_err(csi_dev->dev, - "%s: no source pad in external entity %s\n", __func__, - entity->name); - return -EINVAL; - } - - src_pad_index = ret; - - sink = &csi_dev->video.video_dev.entity; - sink_pad = &csi_dev->video.pad; - - dev_dbg(csi_dev->dev, "creating %s:%u -> %s:%u link\n", - entity->name, src_pad_index, sink->name, sink_pad->index); - ret = media_create_pad_link(entity, src_pad_index, sink, - sink_pad->index, - MEDIA_LNK_FL_ENABLED | - MEDIA_LNK_FL_IMMUTABLE); - if (ret < 0) { - dev_err(csi_dev->dev, "failed to create %s:%u -> %s:%u link\n", - entity->name, src_pad_index, - sink->name, sink_pad->index); - return ret; - } - - return 0; -} - -static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier) -{ - struct sun6i_csi_device *csi_dev = - container_of(notifier, struct sun6i_csi_device, - v4l2.notifier); - struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; - struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; - struct v4l2_subdev *sd; - int ret; - - dev_dbg(csi_dev->dev, "notify complete, all subdevs registered\n"); - - sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list); - if (!sd) - return -EINVAL; - - ret = sun6i_csi_link_entity(csi_dev, &sd->entity, sd->fwnode); - if (ret < 0) - return ret; - - ret = v4l2_device_register_subdev_nodes(v4l2_dev); - if (ret < 0) - return ret; - - return 0; -} - -static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = { - .complete = sun6i_subdev_notify_complete, -}; - -static int sun6i_csi_fwnode_parse(struct device *dev, - struct v4l2_fwnode_endpoint *vep, - struct v4l2_async_subdev *asd) -{ - struct sun6i_csi_device *csi_dev = dev_get_drvdata(dev); - - if (vep->base.port || vep->base.id) { - dev_warn(dev, "Only support a single port with one endpoint\n"); - return -ENOTCONN; - } - - switch (vep->bus_type) { - case V4L2_MBUS_PARALLEL: - case V4L2_MBUS_BT656: - csi_dev->v4l2.v4l2_ep = *vep; - return 0; - default: - dev_err(dev, "Unsupported media bus type\n"); - return -ENOTCONN; - } -} - static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev) { struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; struct media_device *media_dev = &v4l2->media_dev; struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; - struct v4l2_async_notifier *notifier = &v4l2->notifier; struct device *dev = csi_dev->dev; int ret; @@ -709,42 +108,11 @@ static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev) goto error_media; } - /* Video */ - - ret = sun6i_video_setup(csi_dev); - if (ret) - goto error_v4l2_device; - - /* V4L2 Async */ - - v4l2_async_nf_init(notifier); - notifier->ops = &sun6i_csi_async_ops; - - ret = v4l2_async_nf_parse_fwnode_endpoints(dev, notifier, - sizeof(struct - v4l2_async_subdev), - sun6i_csi_fwnode_parse); - if (ret) - goto error_video; - - ret = v4l2_async_nf_register(v4l2_dev, notifier); - if (ret) { - dev_err(dev, "failed to register v4l2 async notifier: %d\n", - ret); - goto error_v4l2_async_notifier; - } + csi_dev->v4l2_dev = v4l2_dev; + csi_dev->media_dev = media_dev; return 0; -error_v4l2_async_notifier: - v4l2_async_nf_cleanup(notifier); - -error_video: - sun6i_video_cleanup(csi_dev); - -error_v4l2_device: - v4l2_device_unregister(&v4l2->v4l2_dev); - error_media: media_device_unregister(media_dev); media_device_cleanup(media_dev); @@ -757,9 +125,6 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev) struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; media_device_unregister(&v4l2->media_dev); - v4l2_async_nf_unregister(&v4l2->notifier); - v4l2_async_nf_cleanup(&v4l2->notifier); - sun6i_video_cleanup(csi_dev); v4l2_device_unregister(&v4l2->v4l2_dev); media_device_cleanup(&v4l2->media_dev); } @@ -769,29 +134,39 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev) static irqreturn_t sun6i_csi_interrupt(int irq, void *private) { struct sun6i_csi_device *csi_dev = private; + bool capture_streaming = csi_dev->capture.state.streaming; struct regmap *regmap = csi_dev->regmap; - u32 status; + u32 status = 0, enable = 0; - regmap_read(regmap, CSI_CH_INT_STA_REG, &status); + regmap_read(regmap, SUN6I_CSI_CH_INT_STA_REG, &status); + regmap_read(regmap, SUN6I_CSI_CH_INT_EN_REG, &enable); - if (!(status & 0xFF)) + if (!status) return IRQ_NONE; - - if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) || - (status & CSI_CH_INT_STA_FIFO1_OF_PD) || - (status & CSI_CH_INT_STA_FIFO2_OF_PD) || - (status & CSI_CH_INT_STA_HB_OF_PD)) { - regmap_write(regmap, CSI_CH_INT_STA_REG, status); - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, - CSI_EN_CSI_EN); + else if (!(status & enable) || !capture_streaming) + goto complete; + + if ((status & SUN6I_CSI_CH_INT_STA_FIFO0_OF) || + (status & SUN6I_CSI_CH_INT_STA_FIFO1_OF) || + (status & SUN6I_CSI_CH_INT_STA_FIFO2_OF) || + (status & SUN6I_CSI_CH_INT_STA_HB_OF)) { + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, status); + + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, + SUN6I_CSI_EN_CSI_EN, 0); + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, + SUN6I_CSI_EN_CSI_EN, SUN6I_CSI_EN_CSI_EN); return IRQ_HANDLED; } - if (status & CSI_CH_INT_STA_FD_PD) - sun6i_video_frame_done(csi_dev); + if (status & SUN6I_CSI_CH_INT_STA_FD) + sun6i_csi_capture_frame_done(csi_dev); + + if (status & SUN6I_CSI_CH_INT_STA_VS) + sun6i_csi_capture_sync(csi_dev); - regmap_write(regmap, CSI_CH_INT_STA_REG, status); +complete: + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, status); return IRQ_HANDLED; } @@ -913,13 +288,12 @@ static int sun6i_csi_resources_setup(struct sun6i_csi_device *csi_dev, irq = platform_get_irq(platform_dev, 0); if (irq < 0) { - dev_err(dev, "failed to get interrupt\n"); ret = -ENXIO; goto error_clock_rate_exclusive; } - ret = devm_request_irq(dev, irq, sun6i_csi_interrupt, 0, SUN6I_CSI_NAME, - csi_dev); + ret = devm_request_irq(dev, irq, sun6i_csi_interrupt, IRQF_SHARED, + SUN6I_CSI_NAME, csi_dev); if (ret) { dev_err(dev, "failed to request interrupt\n"); goto error_clock_rate_exclusive; @@ -960,12 +334,41 @@ static int sun6i_csi_probe(struct platform_device *platform_dev) if (ret) return ret; - ret = sun6i_csi_v4l2_setup(csi_dev); + ret = sun6i_csi_isp_detect(csi_dev); if (ret) goto error_resources; + /* + * Register our own v4l2 and media devices when there is no ISP around. + * Otherwise the ISP will use async subdev registration with our bridge, + * which will provide v4l2 and media devices that are used to register + * the video interface. + */ + if (!csi_dev->isp_available) { + ret = sun6i_csi_v4l2_setup(csi_dev); + if (ret) + goto error_resources; + } + + ret = sun6i_csi_bridge_setup(csi_dev); + if (ret) + goto error_v4l2; + + if (!csi_dev->isp_available) { + ret = sun6i_csi_capture_setup(csi_dev); + if (ret) + goto error_bridge; + } + return 0; +error_bridge: + sun6i_csi_bridge_cleanup(csi_dev); + +error_v4l2: + if (!csi_dev->isp_available) + sun6i_csi_v4l2_cleanup(csi_dev); + error_resources: sun6i_csi_resources_cleanup(csi_dev); @@ -976,7 +379,12 @@ static int sun6i_csi_remove(struct platform_device *pdev) { struct sun6i_csi_device *csi_dev = platform_get_drvdata(pdev); - sun6i_csi_v4l2_cleanup(csi_dev); + sun6i_csi_capture_cleanup(csi_dev); + sun6i_csi_bridge_cleanup(csi_dev); + + if (!csi_dev->isp_available) + sun6i_csi_v4l2_cleanup(csi_dev); + sun6i_csi_resources_cleanup(csi_dev); return 0; @@ -1030,4 +438,5 @@ module_platform_driver(sun6i_csi_platform_driver); MODULE_DESCRIPTION("Allwinner A31 Camera Sensor Interface driver"); MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>"); +MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h index bab705678280..bc3f0dae35df 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h @@ -1,166 +1,63 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ -#ifndef __SUN6I_CSI_H__ -#define __SUN6I_CSI_H__ +#ifndef _SUN6I_CSI_H_ +#define _SUN6I_CSI_H_ #include <media/v4l2-device.h> -#include <media/v4l2-fwnode.h> #include <media/videobuf2-v4l2.h> -#include "sun6i_video.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" #define SUN6I_CSI_NAME "sun6i-csi" #define SUN6I_CSI_DESCRIPTION "Allwinner A31 CSI Device" +enum sun6i_csi_port { + SUN6I_CSI_PORT_PARALLEL = 0, + SUN6I_CSI_PORT_MIPI_CSI2 = 1, + SUN6I_CSI_PORT_ISP = 2, +}; + struct sun6i_csi_buffer { struct vb2_v4l2_buffer v4l2_buffer; struct list_head list; - - dma_addr_t dma_addr; - bool queued_to_csi; -}; - -/** - * struct sun6i_csi_config - configs for sun6i csi - * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*) - * @code: media bus format code (MEDIA_BUS_FMT_*) - * @field: used interlacing type (enum v4l2_field) - * @width: frame width - * @height: frame height - */ -struct sun6i_csi_config { - u32 pixelformat; - u32 code; - u32 field; - u32 width; - u32 height; }; struct sun6i_csi_v4l2 { struct v4l2_device v4l2_dev; struct media_device media_dev; - - struct v4l2_async_notifier notifier; - /* video port settings */ - struct v4l2_fwnode_endpoint v4l2_ep; }; struct sun6i_csi_device { struct device *dev; + struct v4l2_device *v4l2_dev; + struct media_device *media_dev; - struct sun6i_csi_config config; struct sun6i_csi_v4l2 v4l2; - struct sun6i_video video; + struct sun6i_csi_bridge bridge; + struct sun6i_csi_capture capture; struct regmap *regmap; struct clk *clock_mod; struct clk *clock_ram; struct reset_control *reset; - int planar_offset[3]; + bool isp_available; }; struct sun6i_csi_variant { unsigned long clock_mod_rate; }; -/** - * sun6i_csi_is_format_supported() - check if the format supported by csi - * @csi_dev: pointer to the csi device - * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*) - * @mbus_code: media bus format code (MEDIA_BUS_FMT_*) - * - * Return: true if format is supported, false otherwise. - */ -bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, - u32 pixformat, u32 mbus_code); - -/** - * sun6i_csi_set_power() - power on/off the csi - * @csi_dev: pointer to the csi device - * @enable: on/off - * - * Return: 0 if successful, error code otherwise. - */ -int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable); - -/** - * sun6i_csi_update_config() - update the csi register settings - * @csi_dev: pointer to the csi device - * @config: see struct sun6i_csi_config - * - * Return: 0 if successful, error code otherwise. - */ -int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_config *config); - -/** - * sun6i_csi_update_buf_addr() - update the csi frame buffer address - * @csi_dev: pointer to the csi device - * @addr: frame buffer's physical address - */ -void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, - dma_addr_t addr); - -/** - * sun6i_csi_set_stream() - start/stop csi streaming - * @csi_dev: pointer to the csi device - * @enable: start/stop - */ -void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, bool enable); - -/* get bpp form v4l2 pixformat */ -static inline int sun6i_csi_get_bpp(unsigned int pixformat) -{ - switch (pixformat) { - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SGBRG8: - case V4L2_PIX_FMT_SGRBG8: - case V4L2_PIX_FMT_SRGGB8: - case V4L2_PIX_FMT_JPEG: - return 8; - case V4L2_PIX_FMT_SBGGR10: - case V4L2_PIX_FMT_SGBRG10: - case V4L2_PIX_FMT_SGRBG10: - case V4L2_PIX_FMT_SRGGB10: - return 10; - case V4L2_PIX_FMT_SBGGR12: - case V4L2_PIX_FMT_SGBRG12: - case V4L2_PIX_FMT_SGRBG12: - case V4L2_PIX_FMT_SRGGB12: - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - return 12; - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YUV422P: - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_RGB565X: - return 16; - case V4L2_PIX_FMT_RGB24: - case V4L2_PIX_FMT_BGR24: - return 24; - case V4L2_PIX_FMT_RGB32: - case V4L2_PIX_FMT_BGR32: - return 32; - default: - WARN(1, "Unsupported pixformat: 0x%x\n", pixformat); - break; - } +/* ISP */ - return 0; -} +int sun6i_csi_isp_complete(struct sun6i_csi_device *csi_dev, + struct v4l2_device *v4l2_dev); -#endif /* __SUN6I_CSI_H__ */ +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c new file mode 100644 index 000000000000..ebfc870d2af5 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c @@ -0,0 +1,868 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_reg.h" + +/* Helpers */ + +void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = csi_dev->bridge.mbus_format.width; + if (height) + *height = csi_dev->bridge.mbus_format.height; +} + +void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, + u32 *mbus_code, u32 *field) +{ + if (mbus_code) + *mbus_code = csi_dev->bridge.mbus_format.code; + if (field) + *field = csi_dev->bridge.mbus_format.field; +} + +/* Format */ + +static const struct sun6i_csi_bridge_format sun6i_csi_bridge_formats[] = { + /* Bayer */ + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + /* RGB */ + { + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + /* YUV422 */ + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + }, + /* Compressed */ + { + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, +}; + +const struct sun6i_csi_bridge_format * +sun6i_csi_bridge_format_find(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_bridge_formats); i++) + if (sun6i_csi_bridge_formats[i].mbus_code == mbus_code) + return &sun6i_csi_bridge_formats[i]; + + return NULL; +} + +/* Bridge */ + +static void sun6i_csi_bridge_irq_enable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, + SUN6I_CSI_CH_INT_EN_VS | + SUN6I_CSI_CH_INT_EN_HB_OF | + SUN6I_CSI_CH_INT_EN_FIFO2_OF | + SUN6I_CSI_CH_INT_EN_FIFO1_OF | + SUN6I_CSI_CH_INT_EN_FIFO0_OF | + SUN6I_CSI_CH_INT_EN_FD | + SUN6I_CSI_CH_INT_EN_CD); +} + +static void sun6i_csi_bridge_irq_disable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0); +} + +static void sun6i_csi_bridge_irq_clear(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0); + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, + SUN6I_CSI_CH_INT_STA_CLEAR); +} + +static void sun6i_csi_bridge_enable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN, + SUN6I_CSI_EN_CSI_EN); + + regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON, + SUN6I_CSI_CAP_VCAP_ON); +} + +static void sun6i_csi_bridge_disable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON, 0); + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN, 0); +} + +static void +sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) +{ + struct device *dev = csi_dev->dev; + struct regmap *regmap = csi_dev->regmap; + struct v4l2_fwnode_endpoint *endpoint = + &csi_dev->bridge.source_parallel.endpoint; + unsigned char bus_width = endpoint->bus.parallel.bus_width; + unsigned int flags = endpoint->bus.parallel.flags; + u32 field; + u32 value = SUN6I_CSI_IF_CFG_IF_CSI; + + sun6i_csi_bridge_format(csi_dev, NULL, &field); + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED | + SUN6I_CSI_IF_CFG_FIELD_DT_PCLK_SHIFT(1) | + SUN6I_CSI_IF_CFG_FIELD_DT_FIELD_VSYNC; + else + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE; + + switch (endpoint->bus_type) { + case V4L2_MBUS_PARALLEL: + if (bus_width == 16) + value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_COMBINED; + else + value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_RAW; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + value |= SUN6I_CSI_IF_CFG_VREF_POL_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_VREF_POL_POSITIVE; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + value |= SUN6I_CSI_IF_CFG_HREF_POL_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_HREF_POL_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING; + else + value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING; + break; + case V4L2_MBUS_BT656: + if (bus_width == 16) + value |= SUN6I_CSI_IF_CFG_IF_CSI_BT1120; + else + value |= SUN6I_CSI_IF_CFG_IF_CSI_BT656; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING; + else + value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING; + break; + default: + dev_warn(dev, "unsupported bus type: %d\n", endpoint->bus_type); + break; + } + + switch (bus_width) { + case 8: + /* 16-bit YUV formats use a doubled width in 8-bit mode. */ + case 16: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_8; + break; + case 10: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_10; + break; + case 12: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_12; + break; + default: + dev_warn(dev, "unsupported bus width: %u\n", bus_width); + break; + } + + regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); +} + +static void +sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + u32 value = SUN6I_CSI_IF_CFG_IF_MIPI; + u32 field; + + sun6i_csi_bridge_format(csi_dev, NULL, &field); + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED; + else + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE; + + regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); +} + +static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + bool capture_streaming = csi_dev->capture.state.streaming; + const struct sun6i_csi_bridge_format *bridge_format; + const struct sun6i_csi_capture_format *capture_format; + u32 mbus_code, field, pixelformat; + u8 input_format, input_yuv_seq, output_format; + u32 value = 0; + + sun6i_csi_bridge_format(csi_dev, &mbus_code, &field); + + bridge_format = sun6i_csi_bridge_format_find(mbus_code); + if (WARN_ON(!bridge_format)) + return; + + input_format = bridge_format->input_format; + input_yuv_seq = bridge_format->input_yuv_seq; + + if (capture_streaming) { + sun6i_csi_capture_format(csi_dev, &pixelformat, NULL); + + capture_format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!capture_format)) + return; + + if (capture_format->input_format_raw) + input_format = SUN6I_CSI_INPUT_FMT_RAW; + + if (capture_format->input_yuv_seq_invert) + input_yuv_seq = bridge_format->input_yuv_seq_invert; + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + output_format = capture_format->output_format_field; + else + output_format = capture_format->output_format_frame; + + value |= SUN6I_CSI_CH_CFG_OUTPUT_FMT(output_format); + } + + value |= SUN6I_CSI_CH_CFG_INPUT_FMT(input_format); + value |= SUN6I_CSI_CH_CFG_INPUT_YUV_SEQ(input_yuv_seq); + + if (field == V4L2_FIELD_TOP) + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD0; + else if (field == V4L2_FIELD_BOTTOM) + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD1; + else + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_EITHER; + + regmap_write(regmap, SUN6I_CSI_CH_CFG_REG, value); +} + +static void sun6i_csi_bridge_configure(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_bridge_source *source) +{ + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + + if (source == &bridge->source_parallel) + sun6i_csi_bridge_configure_parallel(csi_dev); + else + sun6i_csi_bridge_configure_mipi_csi2(csi_dev); + + sun6i_csi_bridge_configure_format(csi_dev); +} + +/* V4L2 Subdev */ + +static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct media_pad *local_pad = &bridge->pads[SUN6I_CSI_BRIDGE_PAD_SINK]; + bool capture_streaming = csi_dev->capture.state.streaming; + struct device *dev = csi_dev->dev; + struct sun6i_csi_bridge_source *source; + struct v4l2_subdev *source_subdev; + struct media_pad *remote_pad; + int ret; + + /* Source */ + + remote_pad = media_pad_remote_pad_unique(local_pad); + if (IS_ERR(remote_pad)) { + dev_err(dev, + "zero or more than a single source connected to the bridge\n"); + return PTR_ERR(remote_pad); + } + + source_subdev = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (source_subdev == bridge->source_parallel.subdev) + source = &bridge->source_parallel; + else + source = &bridge->source_mipi_csi2; + + if (!on) { + v4l2_subdev_call(source_subdev, video, s_stream, 0); + ret = 0; + goto disable; + } + + /* PM */ + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Clear */ + + sun6i_csi_bridge_irq_clear(csi_dev); + + /* Configure */ + + sun6i_csi_bridge_configure(csi_dev, source); + + if (capture_streaming) + sun6i_csi_capture_configure(csi_dev); + + /* State Update */ + + if (capture_streaming) + sun6i_csi_capture_state_update(csi_dev); + + /* Enable */ + + if (capture_streaming) + sun6i_csi_bridge_irq_enable(csi_dev); + + sun6i_csi_bridge_enable(csi_dev); + + ret = v4l2_subdev_call(source_subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto disable; + + return 0; + +disable: + if (capture_streaming) + sun6i_csi_bridge_irq_disable(csi_dev); + + sun6i_csi_bridge_disable(csi_dev); + + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops sun6i_csi_bridge_video_ops = { + .s_stream = sun6i_csi_bridge_s_stream, +}; + +static void +sun6i_csi_bridge_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) +{ + if (!sun6i_csi_bridge_format_find(mbus_format->code)) + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + + mbus_format->field = V4L2_FIELD_NONE; + mbus_format->colorspace = V4L2_COLORSPACE_RAW; + mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT; + mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_csi_bridge_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + unsigned int pad = SUN6I_CSI_BRIDGE_PAD_SINK; + struct v4l2_mbus_framefmt *mbus_format = + v4l2_subdev_get_try_format(subdev, state, pad); + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + mbus_format->width = 1280; + mbus_format->height = 720; + + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + + mutex_unlock(lock); + + return 0; +} + +static int +sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code_enum) +{ + if (code_enum->index >= ARRAY_SIZE(sun6i_csi_bridge_formats)) + return -EINVAL; + + code_enum->code = sun6i_csi_bridge_formats[code_enum->index].mbus_code; + + return 0; +} + +static int sun6i_csi_bridge_get_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *mbus_format = *v4l2_subdev_get_try_format(subdev, state, + format->pad); + else + *mbus_format = csi_dev->bridge.mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(subdev, state, format->pad) = + *mbus_format; + else + csi_dev->bridge.mbus_format = *mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops sun6i_csi_bridge_pad_ops = { + .init_cfg = sun6i_csi_bridge_init_cfg, + .enum_mbus_code = sun6i_csi_bridge_enum_mbus_code, + .get_fmt = sun6i_csi_bridge_get_fmt, + .set_fmt = sun6i_csi_bridge_set_fmt, +}; + +static const struct v4l2_subdev_ops sun6i_csi_bridge_subdev_ops = { + .video = &sun6i_csi_bridge_video_ops, + .pad = &sun6i_csi_bridge_pad_ops, +}; + +/* Media Entity */ + +static const struct media_entity_operations sun6i_csi_bridge_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* V4L2 Async */ + +static int sun6i_csi_bridge_link(struct sun6i_csi_device *csi_dev, + int sink_pad_index, + struct v4l2_subdev *remote_subdev, + bool enabled) +{ + struct device *dev = csi_dev->dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + struct media_entity *sink_entity = &subdev->entity; + struct media_entity *source_entity = &remote_subdev->entity; + int source_pad_index; + int ret; + + /* Get the first remote source pad. */ + ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "missing source pad in external entity %s\n", + source_entity->name); + return -EINVAL; + } + + source_pad_index = ret; + + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name, + source_pad_index, sink_entity->name, sink_pad_index); + + ret = media_create_pad_link(source_entity, source_pad_index, + sink_entity, sink_pad_index, + enabled ? MEDIA_LNK_FL_ENABLED : 0); + if (ret < 0) { + dev_err(dev, "failed to create %s:%u -> %s:%u link\n", + source_entity->name, source_pad_index, + sink_entity->name, sink_pad_index); + return ret; + } + + return 0; +} + +static int +sun6i_csi_bridge_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *remote_subdev, + struct v4l2_async_subdev *async_subdev) +{ + struct sun6i_csi_device *csi_dev = + container_of(notifier, struct sun6i_csi_device, + bridge.notifier); + struct sun6i_csi_bridge_async_subdev *bridge_async_subdev = + container_of(async_subdev, struct sun6i_csi_bridge_async_subdev, + async_subdev); + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct sun6i_csi_bridge_source *source = bridge_async_subdev->source; + bool enabled = false; + int ret; + + switch (source->endpoint.base.port) { + case SUN6I_CSI_PORT_PARALLEL: + enabled = true; + break; + case SUN6I_CSI_PORT_MIPI_CSI2: + enabled = !bridge->source_parallel.expected; + break; + default: + break; + } + + source->subdev = remote_subdev; + + if (csi_dev->isp_available) { + /* + * Hook to the first available remote subdev to get v4l2 and + * media devices and register the capture device then. + */ + ret = sun6i_csi_isp_complete(csi_dev, remote_subdev->v4l2_dev); + if (ret) + return ret; + } + + return sun6i_csi_bridge_link(csi_dev, SUN6I_CSI_BRIDGE_PAD_SINK, + remote_subdev, enabled); +} + +static int +sun6i_csi_bridge_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct sun6i_csi_device *csi_dev = + container_of(notifier, struct sun6i_csi_device, + bridge.notifier); + struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; + + if (csi_dev->isp_available) + return 0; + + return v4l2_device_register_subdev_nodes(v4l2_dev); +} + +static const struct v4l2_async_notifier_operations +sun6i_csi_bridge_notifier_ops = { + .bound = sun6i_csi_bridge_notifier_bound, + .complete = sun6i_csi_bridge_notifier_complete, +}; + +/* Bridge */ + +static int sun6i_csi_bridge_source_setup(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_bridge_source *source, + u32 port, + enum v4l2_mbus_type *bus_types) +{ + struct device *dev = csi_dev->dev; + struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier; + struct v4l2_fwnode_endpoint *endpoint = &source->endpoint; + struct sun6i_csi_bridge_async_subdev *bridge_async_subdev; + struct fwnode_handle *handle; + int ret; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), port, 0, 0); + if (!handle) + return -ENODEV; + + ret = v4l2_fwnode_endpoint_parse(handle, endpoint); + if (ret) + goto complete; + + if (bus_types) { + bool valid = false; + unsigned int i; + + for (i = 0; bus_types[i] != V4L2_MBUS_INVALID; i++) { + if (endpoint->bus_type == bus_types[i]) { + valid = true; + break; + } + } + + if (!valid) { + dev_err(dev, "unsupported bus type for port %d\n", + port); + ret = -EINVAL; + goto complete; + } + } + + bridge_async_subdev = + v4l2_async_nf_add_fwnode_remote(notifier, handle, + struct + sun6i_csi_bridge_async_subdev); + if (IS_ERR(bridge_async_subdev)) { + ret = PTR_ERR(bridge_async_subdev); + goto complete; + } + + bridge_async_subdev->source = source; + + source->expected = true; + +complete: + fwnode_handle_put(handle); + + return ret; +} + +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) +{ + struct device *dev = csi_dev->dev; + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *subdev = &bridge->subdev; + struct v4l2_async_notifier *notifier = &bridge->notifier; + struct media_pad *pads = bridge->pads; + enum v4l2_mbus_type parallel_mbus_types[] = { + V4L2_MBUS_PARALLEL, + V4L2_MBUS_BT656, + V4L2_MBUS_INVALID + }; + int ret; + + mutex_init(&bridge->lock); + + /* V4L2 Subdev */ + + v4l2_subdev_init(subdev, &sun6i_csi_bridge_subdev_ops); + strscpy(subdev->name, SUN6I_CSI_BRIDGE_NAME, sizeof(subdev->name)); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->owner = THIS_MODULE; + subdev->dev = dev; + + v4l2_set_subdevdata(subdev, csi_dev); + + /* Media Entity */ + + subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + subdev->entity.ops = &sun6i_csi_bridge_entity_ops; + + /* Media Pads */ + + pads[SUN6I_CSI_BRIDGE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[SUN6I_CSI_BRIDGE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&subdev->entity, + SUN6I_CSI_BRIDGE_PAD_COUNT, pads); + if (ret < 0) + return ret; + + /* V4L2 Subdev */ + + if (csi_dev->isp_available) + ret = v4l2_async_register_subdev(subdev); + else + ret = v4l2_device_register_subdev(v4l2_dev, subdev); + + if (ret) { + dev_err(dev, "failed to register v4l2 subdev: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Async */ + + v4l2_async_nf_init(notifier); + notifier->ops = &sun6i_csi_bridge_notifier_ops; + + sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_parallel, + SUN6I_CSI_PORT_PARALLEL, + parallel_mbus_types); + sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_mipi_csi2, + SUN6I_CSI_PORT_MIPI_CSI2, NULL); + + if (csi_dev->isp_available) + ret = v4l2_async_subdev_nf_register(subdev, notifier); + else + ret = v4l2_async_nf_register(v4l2_dev, notifier); + if (ret) { + dev_err(dev, "failed to register v4l2 async notifier: %d\n", + ret); + goto error_v4l2_async_notifier; + } + + return 0; + +error_v4l2_async_notifier: + v4l2_async_nf_cleanup(notifier); + + if (csi_dev->isp_available) + v4l2_async_unregister_subdev(subdev); + else + v4l2_device_unregister_subdev(subdev); + +error_media_entity: + media_entity_cleanup(&subdev->entity); + + return ret; +} + +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier; + + v4l2_async_nf_unregister(notifier); + v4l2_async_nf_cleanup(notifier); + + v4l2_device_unregister_subdev(subdev); + + media_entity_cleanup(&subdev->entity); +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h new file mode 100644 index 000000000000..ee592a14b9c5 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_CSI_BRIDGE_H_ +#define _SUN6I_CSI_BRIDGE_H_ + +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define SUN6I_CSI_BRIDGE_NAME "sun6i-csi-bridge" + +enum sun6i_csi_bridge_pad { + SUN6I_CSI_BRIDGE_PAD_SINK = 0, + SUN6I_CSI_BRIDGE_PAD_SOURCE = 1, + SUN6I_CSI_BRIDGE_PAD_COUNT = 2, +}; + +struct sun6i_csi_device; + +struct sun6i_csi_bridge_format { + u32 mbus_code; + u8 input_format; + u8 input_yuv_seq; + u8 input_yuv_seq_invert; +}; + +struct sun6i_csi_bridge_source { + struct v4l2_subdev *subdev; + struct v4l2_fwnode_endpoint endpoint; + bool expected; +}; + +struct sun6i_csi_bridge_async_subdev { + struct v4l2_async_subdev async_subdev; + struct sun6i_csi_bridge_source *source; +}; + +struct sun6i_csi_bridge { + struct v4l2_subdev subdev; + struct v4l2_async_notifier notifier; + struct media_pad pads[2]; + struct v4l2_mbus_framefmt mbus_format; + struct mutex lock; /* Mbus format lock. */ + + struct sun6i_csi_bridge_source source_parallel; + struct sun6i_csi_bridge_source source_mipi_csi2; +}; + +/* Helpers */ + +void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height); +void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, + u32 *mbus_code, u32 *field); + +/* Format */ + +const struct sun6i_csi_bridge_format * +sun6i_csi_bridge_format_find(u32 mbus_code); + +/* Bridge */ + +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev); +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev); + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c new file mode 100644 index 000000000000..6d34f5c0768f --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c @@ -0,0 +1,1102 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/of.h> +#include <linux/regmap.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-v4l2.h> + +#include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" +#include "sun6i_csi_reg.h" + +/* Helpers */ + +void sun6i_csi_capture_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = csi_dev->capture.format.fmt.pix.width; + if (height) + *height = csi_dev->capture.format.fmt.pix.height; +} + +void sun6i_csi_capture_format(struct sun6i_csi_device *csi_dev, + u32 *pixelformat, u32 *field) +{ + if (pixelformat) + *pixelformat = csi_dev->capture.format.fmt.pix.pixelformat; + + if (field) + *field = csi_dev->capture.format.fmt.pix.field; +} + +/* Format */ + +static const struct sun6i_csi_capture_format sun6i_csi_capture_formats[] = { + /* Bayer */ + { + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + /* RGB */ + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565, + }, + { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565, + }, + /* YUV422 */ + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_NV16, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP, + }, + { + .pixelformat = V4L2_PIX_FMT_NV61, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP, + .input_yuv_seq_invert = true, + }, + { + .pixelformat = V4L2_PIX_FMT_YUV422P, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422P, + }, + /* YUV420 */ + { + .pixelformat = V4L2_PIX_FMT_NV12_16L16, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420MB, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420MB, + }, + { + .pixelformat = V4L2_PIX_FMT_NV12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP, + }, + { + .pixelformat = V4L2_PIX_FMT_NV21, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP, + .input_yuv_seq_invert = true, + }, + + { + .pixelformat = V4L2_PIX_FMT_YUV420, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P, + }, + { + .pixelformat = V4L2_PIX_FMT_YVU420, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P, + .input_yuv_seq_invert = true, + }, + /* Compressed */ + { + .pixelformat = V4L2_PIX_FMT_JPEG, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, +}; + +const +struct sun6i_csi_capture_format *sun6i_csi_capture_format_find(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_capture_formats); i++) + if (sun6i_csi_capture_formats[i].pixelformat == pixelformat) + return &sun6i_csi_capture_formats[i]; + + return NULL; +} + +/* RAW formats need an exact match between pixel and mbus formats. */ +static const +struct sun6i_csi_capture_format_match sun6i_csi_capture_format_matches[] = { + /* YUV420 */ + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + }, + /* RGB */ + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + }, + { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE, + }, + /* Bayer */ + { + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + }, + /* Compressed */ + { + .pixelformat = V4L2_PIX_FMT_JPEG, + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + }, +}; + +static bool sun6i_csi_capture_format_match(u32 pixelformat, u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_capture_format_matches); i++) { + const struct sun6i_csi_capture_format_match *match = + &sun6i_csi_capture_format_matches[i]; + + if (match->pixelformat == pixelformat && + match->mbus_code == mbus_code) + return true; + } + + return false; +} + +/* Capture */ + +static void +sun6i_csi_capture_buffer_configure(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_buffer *csi_buffer) +{ + struct regmap *regmap = csi_dev->regmap; + const struct v4l2_format_info *info; + struct vb2_buffer *vb2_buffer; + unsigned int width, height; + dma_addr_t address; + u32 pixelformat; + + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + address = vb2_dma_contig_plane_dma_addr(vb2_buffer, 0); + + regmap_write(regmap, SUN6I_CSI_CH_FIFO0_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + + sun6i_csi_capture_dimensions(csi_dev, &width, &height); + sun6i_csi_capture_format(csi_dev, &pixelformat, NULL); + + info = v4l2_format_info(pixelformat); + /* Unsupported formats are single-plane, so we can stop here. */ + if (!info) + return; + + if (info->comp_planes > 1) { + address += info->bpp[0] * width * height; + + regmap_write(regmap, SUN6I_CSI_CH_FIFO1_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + } + + if (info->comp_planes > 2) { + address += info->bpp[1] * DIV_ROUND_UP(width, info->hdiv) * + DIV_ROUND_UP(height, info->vdiv); + + regmap_write(regmap, SUN6I_CSI_CH_FIFO2_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + } +} + +void sun6i_csi_capture_configure(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + const struct sun6i_csi_capture_format *format; + const struct v4l2_format_info *info; + u32 hsize_len, vsize_len; + u32 luma_line, chroma_line = 0; + u32 pixelformat, field; + u32 width, height; + + sun6i_csi_capture_dimensions(csi_dev, &width, &height); + sun6i_csi_capture_format(csi_dev, &pixelformat, &field); + + format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!format)) + return; + + hsize_len = width; + vsize_len = height; + + /* + * When using 8-bit raw input/output (for packed YUV), we need to adapt + * the width to account for the difference in bpp when it's not 8-bit. + */ + if (format->hsize_len_factor) + hsize_len *= format->hsize_len_factor; + + regmap_write(regmap, SUN6I_CSI_CH_HSIZE_REG, + SUN6I_CSI_CH_HSIZE_LEN(hsize_len) | + SUN6I_CSI_CH_HSIZE_START(0)); + + regmap_write(regmap, SUN6I_CSI_CH_VSIZE_REG, + SUN6I_CSI_CH_VSIZE_LEN(vsize_len) | + SUN6I_CSI_CH_VSIZE_START(0)); + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565X: + luma_line = width * 2; + break; + case V4L2_PIX_FMT_NV12_16L16: + luma_line = width; + chroma_line = width; + break; + case V4L2_PIX_FMT_JPEG: + luma_line = width; + break; + default: + info = v4l2_format_info(pixelformat); + if (WARN_ON(!info)) + return; + + luma_line = width * info->bpp[0]; + + if (info->comp_planes > 1) + chroma_line = width * info->bpp[1] / info->hdiv; + break; + } + + regmap_write(regmap, SUN6I_CSI_CH_BUF_LEN_REG, + SUN6I_CSI_CH_BUF_LEN_CHROMA_LINE(chroma_line) | + SUN6I_CSI_CH_BUF_LEN_LUMA_LINE(luma_line)); +} + +/* State */ + +static void sun6i_csi_capture_state_cleanup(struct sun6i_csi_device *csi_dev, + bool error) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct sun6i_csi_buffer **csi_buffer_states[] = { + &state->pending, &state->current, &state->complete, + }; + struct sun6i_csi_buffer *csi_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&state->lock, flags); + + for (i = 0; i < ARRAY_SIZE(csi_buffer_states); i++) { + csi_buffer = *csi_buffer_states[i]; + if (!csi_buffer) + continue; + + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + + *csi_buffer_states[i] = NULL; + } + + list_for_each_entry(csi_buffer, &state->queue, list) { + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + INIT_LIST_HEAD(&state->queue); + + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_state_update(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct sun6i_csi_buffer *csi_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (list_empty(&state->queue)) + goto complete; + + if (state->pending) + goto complete; + + csi_buffer = list_first_entry(&state->queue, struct sun6i_csi_buffer, + list); + + sun6i_csi_capture_buffer_configure(csi_dev, csi_buffer); + + list_del(&csi_buffer->list); + + state->pending = csi_buffer; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +static void sun6i_csi_capture_state_complete(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (!state->pending) + goto complete; + + state->complete = state->current; + state->current = state->pending; + state->pending = NULL; + + if (state->complete) { + struct sun6i_csi_buffer *csi_buffer = state->complete; + struct vb2_buffer *vb2_buffer = + &csi_buffer->v4l2_buffer.vb2_buf; + + vb2_buffer->timestamp = ktime_get_ns(); + csi_buffer->v4l2_buffer.sequence = state->sequence; + + vb2_buffer_done(vb2_buffer, VB2_BUF_STATE_DONE); + + state->complete = NULL; + } + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_frame_done(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + state->sequence++; + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_sync(struct sun6i_csi_device *csi_dev) +{ + sun6i_csi_capture_state_complete(csi_dev); + sun6i_csi_capture_state_update(csi_dev); +} + +/* Queue */ + +static int sun6i_csi_capture_queue_setup(struct vb2_queue *queue, + unsigned int *buffers_count, + unsigned int *planes_count, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + unsigned int size = csi_dev->capture.format.fmt.pix.sizeimage; + + if (*planes_count) + return sizes[0] < size ? -EINVAL : 0; + + *planes_count = 1; + sizes[0] = size; + + return 0; +} + +static int sun6i_csi_capture_buffer_prepare(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + unsigned long size = capture->format.fmt.pix.sizeimage; + + if (vb2_plane_size(buffer, 0) < size) { + v4l2_err(v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(buffer, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(buffer, 0, size); + + v4l2_buffer->field = capture->format.fmt.pix.field; + + return 0; +} + +static void sun6i_csi_capture_buffer_queue(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + struct sun6i_csi_buffer *csi_buffer = + container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + list_add_tail(&csi_buffer->list, &state->queue); + spin_unlock_irqrestore(&state->lock, flags); +} + +static int sun6i_csi_capture_start_streaming(struct vb2_queue *queue, + unsigned int count) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct video_device *video_dev = &csi_dev->capture.video_dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + int ret; + + state->sequence = 0; + + ret = video_device_pipeline_alloc_start(video_dev); + if (ret < 0) + goto error_state; + + state->streaming = true; + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto error_streaming; + + return 0; + +error_streaming: + state->streaming = false; + + video_device_pipeline_stop(video_dev); + +error_state: + sun6i_csi_capture_state_cleanup(csi_dev, false); + + return ret; +} + +static void sun6i_csi_capture_stop_streaming(struct vb2_queue *queue) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct video_device *video_dev = &csi_dev->capture.video_dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + + v4l2_subdev_call(subdev, video, s_stream, 0); + + state->streaming = false; + + video_device_pipeline_stop(video_dev); + + sun6i_csi_capture_state_cleanup(csi_dev, true); +} + +static const struct vb2_ops sun6i_csi_capture_queue_ops = { + .queue_setup = sun6i_csi_capture_queue_setup, + .buf_prepare = sun6i_csi_capture_buffer_prepare, + .buf_queue = sun6i_csi_capture_buffer_queue, + .start_streaming = sun6i_csi_capture_start_streaming, + .stop_streaming = sun6i_csi_capture_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* V4L2 Device */ + +static void sun6i_csi_capture_format_prepare(struct v4l2_format *format) +{ + struct v4l2_pix_format *pix_format = &format->fmt.pix; + const struct v4l2_format_info *info; + unsigned int width, height; + + v4l_bound_align_image(&pix_format->width, SUN6I_CSI_CAPTURE_WIDTH_MIN, + SUN6I_CSI_CAPTURE_WIDTH_MAX, 1, + &pix_format->height, SUN6I_CSI_CAPTURE_HEIGHT_MIN, + SUN6I_CSI_CAPTURE_HEIGHT_MAX, 1, 0); + + if (!sun6i_csi_capture_format_find(pix_format->pixelformat)) + pix_format->pixelformat = + sun6i_csi_capture_formats[0].pixelformat; + + width = pix_format->width; + height = pix_format->height; + + info = v4l2_format_info(pix_format->pixelformat); + + switch (pix_format->pixelformat) { + case V4L2_PIX_FMT_NV12_16L16: + pix_format->bytesperline = width * 12 / 8; + pix_format->sizeimage = pix_format->bytesperline * height; + break; + case V4L2_PIX_FMT_JPEG: + pix_format->bytesperline = width; + pix_format->sizeimage = pix_format->bytesperline * height; + break; + default: + v4l2_fill_pixfmt(pix_format, pix_format->pixelformat, + width, height); + break; + } + + if (pix_format->field == V4L2_FIELD_ANY) + pix_format->field = V4L2_FIELD_NONE; + + if (pix_format->pixelformat == V4L2_PIX_FMT_JPEG) + pix_format->colorspace = V4L2_COLORSPACE_JPEG; + else if (info && info->pixel_enc == V4L2_PIXEL_ENC_BAYER) + pix_format->colorspace = V4L2_COLORSPACE_RAW; + else + pix_format->colorspace = V4L2_COLORSPACE_SRGB; + + pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; + pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_csi_capture_querycap(struct file *file, void *private, + struct v4l2_capability *capability) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct video_device *video_dev = &csi_dev->capture.video_dev; + + strscpy(capability->driver, SUN6I_CSI_NAME, sizeof(capability->driver)); + strscpy(capability->card, video_dev->name, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", dev_name(csi_dev->dev)); + + return 0; +} + +static int sun6i_csi_capture_enum_fmt(struct file *file, void *private, + struct v4l2_fmtdesc *fmtdesc) +{ + u32 index = fmtdesc->index; + + if (index >= ARRAY_SIZE(sun6i_csi_capture_formats)) + return -EINVAL; + + fmtdesc->pixelformat = sun6i_csi_capture_formats[index].pixelformat; + + return 0; +} + +static int sun6i_csi_capture_g_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + + *format = csi_dev->capture.format; + + return 0; +} + +static int sun6i_csi_capture_s_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + + if (vb2_is_busy(&capture->queue)) + return -EBUSY; + + sun6i_csi_capture_format_prepare(format); + + csi_dev->capture.format = *format; + + return 0; +} + +static int sun6i_csi_capture_try_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + sun6i_csi_capture_format_prepare(format); + + return 0; +} + +static int sun6i_csi_capture_enum_input(struct file *file, void *private, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, "Camera", sizeof(input->name)); + + return 0; +} + +static int sun6i_csi_capture_g_input(struct file *file, void *private, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int sun6i_csi_capture_s_input(struct file *file, void *private, + unsigned int index) +{ + if (index != 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_ioctl_ops sun6i_csi_capture_ioctl_ops = { + .vidioc_querycap = sun6i_csi_capture_querycap, + + .vidioc_enum_fmt_vid_cap = sun6i_csi_capture_enum_fmt, + .vidioc_g_fmt_vid_cap = sun6i_csi_capture_g_fmt, + .vidioc_s_fmt_vid_cap = sun6i_csi_capture_s_fmt, + .vidioc_try_fmt_vid_cap = sun6i_csi_capture_try_fmt, + + .vidioc_enum_input = sun6i_csi_capture_enum_input, + .vidioc_g_input = sun6i_csi_capture_g_input, + .vidioc_s_input = sun6i_csi_capture_s_input, + + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* V4L2 File */ + +static int sun6i_csi_capture_open(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + int ret = 0; + + if (mutex_lock_interruptible(&capture->lock)) + return -ERESTARTSYS; + + ret = v4l2_pipeline_pm_get(&capture->video_dev.entity); + if (ret < 0) + goto error_lock; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto error_pipeline; + + mutex_unlock(&capture->lock); + + return 0; + +error_pipeline: + v4l2_pipeline_pm_put(&capture->video_dev.entity); + +error_lock: + mutex_unlock(&capture->lock); + + return ret; +} + +static int sun6i_csi_capture_close(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + + mutex_lock(&capture->lock); + + _vb2_fop_release(file, NULL); + v4l2_pipeline_pm_put(&capture->video_dev.entity); + + mutex_unlock(&capture->lock); + + return 0; +} + +static const struct v4l2_file_operations sun6i_csi_capture_fops = { + .owner = THIS_MODULE, + .open = sun6i_csi_capture_open, + .release = sun6i_csi_capture_close, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll +}; + +/* Media Entity */ + +static int sun6i_csi_capture_link_validate(struct media_link *link) +{ + struct video_device *video_dev = + media_entity_to_video_device(link->sink->entity); + struct sun6i_csi_device *csi_dev = video_get_drvdata(video_dev); + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + const struct sun6i_csi_capture_format *capture_format; + const struct sun6i_csi_bridge_format *bridge_format; + unsigned int capture_width, capture_height; + unsigned int bridge_width, bridge_height; + const struct v4l2_format_info *format_info; + u32 pixelformat, capture_field; + u32 mbus_code, bridge_field; + bool match; + + sun6i_csi_capture_dimensions(csi_dev, &capture_width, &capture_height); + + sun6i_csi_capture_format(csi_dev, &pixelformat, &capture_field); + capture_format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!capture_format)) + return -EINVAL; + + sun6i_csi_bridge_dimensions(csi_dev, &bridge_width, &bridge_height); + + sun6i_csi_bridge_format(csi_dev, &mbus_code, &bridge_field); + bridge_format = sun6i_csi_bridge_format_find(mbus_code); + if (WARN_ON(!bridge_format)) + return -EINVAL; + + /* No cropping/scaling is supported. */ + if (capture_width != bridge_width || capture_height != bridge_height) { + v4l2_err(v4l2_dev, + "invalid input/output dimensions: %ux%u/%ux%u\n", + bridge_width, bridge_height, capture_width, + capture_height); + return -EINVAL; + } + + format_info = v4l2_format_info(pixelformat); + /* Some formats are not listed. */ + if (!format_info) + return 0; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_BAYER && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_RAW) + goto invalid; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_RGB && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_RAW) + goto invalid; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_YUV) { + if (bridge_format->input_format != SUN6I_CSI_INPUT_FMT_YUV420 && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_YUV422) + goto invalid; + + /* YUV420 input can't produce YUV422 output. */ + if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_YUV420 && + format_info->vdiv == 1) + goto invalid; + } + + /* With raw input mode, we need a 1:1 match between input and output. */ + if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_RAW || + capture_format->input_format_raw) { + match = sun6i_csi_capture_format_match(pixelformat, mbus_code); + if (!match) + goto invalid; + } + + return 0; + +invalid: + v4l2_err(v4l2_dev, "invalid input/output format combination\n"); + return -EINVAL; +} + +static const struct media_entity_operations sun6i_csi_capture_media_ops = { + .link_validate = sun6i_csi_capture_link_validate +}; + +/* Capture */ + +int sun6i_csi_capture_setup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct sun6i_csi_capture_state *state = &capture->state; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *bridge_subdev = &csi_dev->bridge.subdev; + struct video_device *video_dev = &capture->video_dev; + struct vb2_queue *queue = &capture->queue; + struct media_pad *pad = &capture->pad; + struct v4l2_format *format = &csi_dev->capture.format; + struct v4l2_pix_format *pix_format = &format->fmt.pix; + int ret; + + /* This may happen with multiple bridge notifier bound calls. */ + if (state->setup) + return 0; + + /* State */ + + INIT_LIST_HEAD(&state->queue); + spin_lock_init(&state->lock); + + /* Media Entity */ + + video_dev->entity.ops = &sun6i_csi_capture_media_ops; + + /* Media Pad */ + + pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&video_dev->entity, 1, pad); + if (ret < 0) + return ret; + + /* Queue */ + + mutex_init(&capture->lock); + + queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + queue->io_modes = VB2_MMAP | VB2_DMABUF; + queue->buf_struct_size = sizeof(struct sun6i_csi_buffer); + queue->ops = &sun6i_csi_capture_queue_ops; + queue->mem_ops = &vb2_dma_contig_memops; + queue->min_buffers_needed = 2; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->lock = &capture->lock; + queue->dev = csi_dev->dev; + queue->drv_priv = csi_dev; + + ret = vb2_queue_init(queue); + if (ret) { + v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Format */ + + format->type = queue->type; + pix_format->pixelformat = sun6i_csi_capture_formats[0].pixelformat; + pix_format->width = 1280; + pix_format->height = 720; + pix_format->field = V4L2_FIELD_NONE; + + sun6i_csi_capture_format_prepare(format); + + /* Video Device */ + + strscpy(video_dev->name, SUN6I_CSI_CAPTURE_NAME, + sizeof(video_dev->name)); + video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + video_dev->vfl_dir = VFL_DIR_RX; + video_dev->release = video_device_release_empty; + video_dev->fops = &sun6i_csi_capture_fops; + video_dev->ioctl_ops = &sun6i_csi_capture_ioctl_ops; + video_dev->v4l2_dev = v4l2_dev; + video_dev->queue = queue; + video_dev->lock = &capture->lock; + + video_set_drvdata(video_dev, csi_dev); + + ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to register video device: %d\n", + ret); + goto error_media_entity; + } + + /* Media Pad Link */ + + ret = media_create_pad_link(&bridge_subdev->entity, + SUN6I_CSI_BRIDGE_PAD_SOURCE, + &video_dev->entity, 0, + csi_dev->isp_available ? 0 : + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n", + bridge_subdev->entity.name, + SUN6I_CSI_BRIDGE_PAD_SOURCE, + video_dev->entity.name, 0); + goto error_video_device; + } + + state->setup = true; + + return 0; + +error_video_device: + vb2_video_unregister_device(video_dev); + +error_media_entity: + media_entity_cleanup(&video_dev->entity); + + mutex_destroy(&capture->lock); + + return ret; +} + +void sun6i_csi_capture_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct video_device *video_dev = &capture->video_dev; + + /* This may happen if async registration failed to complete. */ + if (!capture->state.setup) + return; + + vb2_video_unregister_device(video_dev); + media_entity_cleanup(&video_dev->entity); + mutex_destroy(&capture->lock); + + capture->state.setup = false; +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h new file mode 100644 index 000000000000..3ee5ccefbd10 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_CAPTURE_H_ +#define _SUN6I_CAPTURE_H_ + +#include <media/v4l2-device.h> + +#define SUN6I_CSI_CAPTURE_NAME "sun6i-csi-capture" + +#define SUN6I_CSI_CAPTURE_WIDTH_MIN 32 +#define SUN6I_CSI_CAPTURE_WIDTH_MAX 4800 +#define SUN6I_CSI_CAPTURE_HEIGHT_MIN 32 +#define SUN6I_CSI_CAPTURE_HEIGHT_MAX 4800 + +struct sun6i_csi_device; + +struct sun6i_csi_capture_format { + u32 pixelformat; + u8 output_format_field; + u8 output_format_frame; + bool input_yuv_seq_invert; + bool input_format_raw; + u32 hsize_len_factor; +}; + +struct sun6i_csi_capture_format_match { + u32 pixelformat; + u32 mbus_code; +}; + +#undef current +struct sun6i_csi_capture_state { + struct list_head queue; + spinlock_t lock; /* Queue and buffers lock. */ + + struct sun6i_csi_buffer *pending; + struct sun6i_csi_buffer *current; + struct sun6i_csi_buffer *complete; + + unsigned int sequence; + bool streaming; + bool setup; +}; + +struct sun6i_csi_capture { + struct sun6i_csi_capture_state state; + + struct video_device video_dev; + struct vb2_queue queue; + struct mutex lock; /* Queue lock. */ + struct media_pad pad; + + struct v4l2_format format; +}; + +/* Helpers */ + +void sun6i_csi_capture_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height); +void sun6i_csi_capture_format(struct sun6i_csi_device *csi_dev, + u32 *pixelformat, u32 *field); + +/* Format */ + +const +struct sun6i_csi_capture_format *sun6i_csi_capture_format_find(u32 pixelformat); + +/* Capture */ + +void sun6i_csi_capture_configure(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_state_update(struct sun6i_csi_device *csi_dev); + +/* State */ + +void sun6i_csi_capture_sync(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_frame_done(struct sun6i_csi_device *csi_dev); + +/* Capture */ + +int sun6i_csi_capture_setup(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_cleanup(struct sun6i_csi_device *csi_dev); + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h index 703fa14bb313..e01c5b9c2d60 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h @@ -1,196 +1,184 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ -#ifndef __SUN6I_CSI_REG_H__ -#define __SUN6I_CSI_REG_H__ +#ifndef _SUN6I_CSI_REG_H_ +#define _SUN6I_CSI_REG_H_ #include <linux/kernel.h> -#define CSI_EN_REG 0x0 -#define CSI_EN_VER_EN BIT(30) -#define CSI_EN_CSI_EN BIT(0) - -#define CSI_IF_CFG_REG 0x4 -#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21) -#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) -#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) -#define CSI_IF_CFG_FPS_DS_EN BIT(20) -#define CSI_IF_CFG_FIELD_MASK BIT(19) -#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK) -#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK) -#define CSI_IF_CFG_VREF_POL_MASK BIT(18) -#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK) -#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK) -#define CSI_IF_CFG_HREF_POL_MASK BIT(17) -#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK) -#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK) -#define CSI_IF_CFG_CLK_POL_MASK BIT(16) -#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK) -#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK) -#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8) -#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) -#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) -#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) -#define CSI_IF_CFG_MIPI_IF_MASK BIT(7) -#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7) -#define CSI_IF_CFG_MIPI_IF_MIPI BIT(7) -#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0) -#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK) -#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK) -#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK) -#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK) - -#define CSI_CAP_REG 0x8 -#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2) -#define CSI_CAP_CH0_CAP_MASK(count) (((count) << 2) & CSI_CAP_CH0_CAP_MASK_MASK) -#define CSI_CAP_CH0_VCAP_ON BIT(1) -#define CSI_CAP_CH0_SCAP_ON BIT(0) - -#define CSI_SYNC_CNT_REG 0xc -#define CSI_FIFO_THRS_REG 0x10 -#define CSI_BT656_HEAD_CFG_REG 0x14 -#define CSI_PTN_LEN_REG 0x30 -#define CSI_PTN_ADDR_REG 0x34 -#define CSI_VER_REG 0x3c - -#define CSI_CH_CFG_REG 0x44 -#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20) -#define CSI_CH_CFG_INPUT_FMT(fmt) (((fmt) << 20) & CSI_CH_CFG_INPUT_FMT_MASK) -#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16) -#define CSI_CH_CFG_OUTPUT_FMT(fmt) (((fmt) << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK) -#define CSI_CH_CFG_VFLIP_EN BIT(13) -#define CSI_CH_CFG_HFLIP_EN BIT(12) -#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10) -#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) -#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) -#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) -#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8) -#define CSI_CH_CFG_INPUT_SEQ(seq) (((seq) << 8) & CSI_CH_CFG_INPUT_SEQ_MASK) - -#define CSI_CH_SCALE_REG 0x4c -#define CSI_CH_SCALE_QUART_EN BIT(0) - -#define CSI_CH_F0_BUFA_REG 0x50 - -#define CSI_CH_F1_BUFA_REG 0x58 - -#define CSI_CH_F2_BUFA_REG 0x60 - -#define CSI_CH_STA_REG 0x6c -#define CSI_CH_STA_FIELD_STA_MASK BIT(2) -#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK) -#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK) -#define CSI_CH_STA_VCAP_STA BIT(1) -#define CSI_CH_STA_SCAP_STA BIT(0) - -#define CSI_CH_INT_EN_REG 0x70 -#define CSI_CH_INT_EN_VS_INT_EN BIT(7) -#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6) -#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5) -#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4) -#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3) -#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2) -#define CSI_CH_INT_EN_FD_INT_EN BIT(1) -#define CSI_CH_INT_EN_CD_INT_EN BIT(0) - -#define CSI_CH_INT_STA_REG 0x74 -#define CSI_CH_INT_STA_VS_PD BIT(7) -#define CSI_CH_INT_STA_HB_OF_PD BIT(6) -#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5) -#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4) -#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3) -#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2) -#define CSI_CH_INT_STA_FD_PD BIT(1) -#define CSI_CH_INT_STA_CD_PD BIT(0) - -#define CSI_CH_FLD1_VSIZE_REG 0x78 - -#define CSI_CH_HSIZE_REG 0x80 -#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16) -#define CSI_CH_HSIZE_HOR_LEN(len) (((len) << 16) & CSI_CH_HSIZE_HOR_LEN_MASK) -#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0) -#define CSI_CH_HSIZE_HOR_START(start) (((start) << 0) & CSI_CH_HSIZE_HOR_START_MASK) - -#define CSI_CH_VSIZE_REG 0x84 -#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16) -#define CSI_CH_VSIZE_VER_LEN(len) (((len) << 16) & CSI_CH_VSIZE_VER_LEN_MASK) -#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0) -#define CSI_CH_VSIZE_VER_START(start) (((start) << 0) & CSI_CH_VSIZE_VER_START_MASK) - -#define CSI_CH_BUF_LEN_REG 0x88 -#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16) -#define CSI_CH_BUF_LEN_BUF_LEN_C(len) (((len) << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK) -#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0) -#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) (((len) << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK) - -#define CSI_CH_FLIP_SIZE_REG 0x8c -#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16) -#define CSI_CH_FLIP_SIZE_VER_LEN(len) (((len) << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK) -#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0) -#define CSI_CH_FLIP_SIZE_VALID_LEN(len) (((len) << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK) - -#define CSI_CH_FRM_CLK_CNT_REG 0x90 -#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94 -#define CSI_CH_FIFO_STAT_REG 0x98 -#define CSI_CH_PCLK_STAT_REG 0x9c - -/* - * csi input data format - */ -enum csi_input_fmt { - CSI_INPUT_FORMAT_RAW = 0, - CSI_INPUT_FORMAT_YUV422 = 3, - CSI_INPUT_FORMAT_YUV420 = 4, -}; - -/* - * csi output data format - */ -enum csi_output_fmt { - /* only when input format is RAW */ - CSI_FIELD_RAW_8 = 0, - CSI_FIELD_RAW_10 = 1, - CSI_FIELD_RAW_12 = 2, - CSI_FIELD_RGB565 = 4, - CSI_FIELD_RGB888 = 5, - CSI_FIELD_PRGB888 = 6, - CSI_FRAME_RAW_8 = 8, - CSI_FRAME_RAW_10 = 9, - CSI_FRAME_RAW_12 = 10, - CSI_FRAME_RGB565 = 12, - CSI_FRAME_RGB888 = 13, - CSI_FRAME_PRGB888 = 14, - - /* only when input format is YUV422 */ - CSI_FIELD_PLANAR_YUV422 = 0, - CSI_FIELD_PLANAR_YUV420 = 1, - CSI_FRAME_PLANAR_YUV420 = 2, - CSI_FRAME_PLANAR_YUV422 = 3, - CSI_FIELD_UV_CB_YUV422 = 4, - CSI_FIELD_UV_CB_YUV420 = 5, - CSI_FRAME_UV_CB_YUV420 = 6, - CSI_FRAME_UV_CB_YUV422 = 7, - CSI_FIELD_MB_YUV422 = 8, - CSI_FIELD_MB_YUV420 = 9, - CSI_FRAME_MB_YUV420 = 10, - CSI_FRAME_MB_YUV422 = 11, - CSI_FIELD_UV_CB_YUV422_10 = 12, - CSI_FIELD_UV_CB_YUV420_10 = 13, -}; - -/* - * csi YUV input data sequence - */ -enum csi_input_seq { - /* only when input format is YUV422 */ - CSI_INPUT_SEQ_YUYV = 0, - CSI_INPUT_SEQ_YVYU, - CSI_INPUT_SEQ_UYVY, - CSI_INPUT_SEQ_VYUY, -}; - -#endif /* __SUN6I_CSI_REG_H__ */ +#define SUN6I_CSI_ADDR_VALUE(a) ((a) >> 2) + +#define SUN6I_CSI_EN_REG 0x0 +#define SUN6I_CSI_EN_VER_EN BIT(30) +#define SUN6I_CSI_EN_PTN_CYCLE(v) (((v) << 16) & GENMASK(23, 16)) +#define SUN6I_CSI_EN_SRAM_PWDN BIT(8) +#define SUN6I_CSI_EN_PTN_START BIT(4) +#define SUN6I_CSI_EN_CLK_CNT_SPL_VSYNC BIT(3) +#define SUN6I_CSI_EN_CLK_CNT_EN BIT(2) +#define SUN6I_CSI_EN_PTN_GEN_EN BIT(1) +#define SUN6I_CSI_EN_CSI_EN BIT(0) + +/* Note that Allwinner manuals and code invert positive/negative definitions. */ + +#define SUN6I_CSI_IF_CFG_REG 0x4 +#define SUN6I_CSI_IF_CFG_FIELD_DT_PCLK_SHIFT(v) (((v) << 24) & GENMASK(27, 24)) +#define SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE (0 << 21) +#define SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED (1 << 21) +#define SUN6I_CSI_IF_CFG_FPS_DS BIT(20) +#define SUN6I_CSI_IF_CFG_FIELD_POSITIVE (0 << 19) +#define SUN6I_CSI_IF_CFG_FIELD_NEGATIVE (1 << 19) +#define SUN6I_CSI_IF_CFG_VREF_POL_POSITIVE (0 << 18) +#define SUN6I_CSI_IF_CFG_VREF_POL_NEGATIVE (1 << 18) +#define SUN6I_CSI_IF_CFG_HREF_POL_POSITIVE (0 << 17) +#define SUN6I_CSI_IF_CFG_HREF_POL_NEGATIVE (1 << 17) +#define SUN6I_CSI_IF_CFG_CLK_POL_FALLING (0 << 16) +#define SUN6I_CSI_IF_CFG_CLK_POL_RISING (1 << 16) +#define SUN6I_CSI_IF_CFG_FIELD_DT_FIELD_VSYNC (0 << 14) +#define SUN6I_CSI_IF_CFG_FIELD_DT_FIELD (1 << 14) +#define SUN6I_CSI_IF_CFG_FIELD_DT_VSYNC (2 << 14) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_8 (0 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_10 (1 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_12 (2 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_8_PLUS_2 (3 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_2_TIMES_8 (4 << 8) +#define SUN6I_CSI_IF_CFG_IF_CSI (0 << 7) +#define SUN6I_CSI_IF_CFG_IF_MIPI (1 << 7) +#define SUN6I_CSI_IF_CFG_IF_CSI_YUV_RAW (0 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_YUV_COMBINED (1 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_BT656 (4 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_BT1120 (5 << 0) + +#define SUN6I_CSI_CAP_REG 0x8 +#define SUN6I_CSI_CAP_MASK(v) (((v) << 2) & GENMASK(5, 2)) +#define SUN6I_CSI_CAP_VCAP_ON BIT(1) +#define SUN6I_CSI_CAP_SCAP_ON BIT(0) + +#define SUN6I_CSI_SYNC_CNT_REG 0xc +#define SUN6I_CSI_FIFO_THRS_REG 0x10 +#define SUN6I_CSI_BT656_HEAD_CFG_REG 0x14 + +#define SUN6I_CSI_PTN_LEN_REG 0x30 +#define SUN6I_CSI_PTN_ADDR_REG 0x34 +#define SUN6I_CSI_VER_REG 0x3c + +#define SUN6I_CSI_CH_CFG_REG 0x44 +#define SUN6I_CSI_CH_CFG_PAD_VAL(v) (((v) << 24) & GENMASK(31, 24)) +#define SUN6I_CSI_CH_CFG_INPUT_FMT(v) (((v) << 20) & GENMASK(23, 20)) +#define SUN6I_CSI_CH_CFG_OUTPUT_FMT(v) (((v) << 16) & GENMASK(19, 16)) +#define SUN6I_CSI_CH_CFG_VFLIP_EN BIT(13) +#define SUN6I_CSI_CH_CFG_HFLIP_EN BIT(12) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD0 (0 << 10) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD1 (1 << 10) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_EITHER (2 << 10) +#define SUN6I_CSI_CH_CFG_INPUT_YUV_SEQ(v) (((v) << 8) & GENMASK(9, 8)) + +#define SUN6I_CSI_INPUT_FMT_RAW 0 +#define SUN6I_CSI_INPUT_FMT_YUV422 3 +#define SUN6I_CSI_INPUT_FMT_YUV420 4 + +/* Note that Allwinner manuals and code invert frame/field definitions. */ + +/* RAW */ +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8 0 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10 1 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12 2 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565 4 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RGB888 5 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_PRGB888 6 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8 8 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10 9 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12 10 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565 12 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RGB888 13 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_PRGB888 14 + +/* YUV */ +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422P 0 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P 1 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P 2 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422P 3 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP 4 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP 5 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP 6 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP 7 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422MB 8 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420MB 9 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420MB 10 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422MB 11 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP_10 12 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP_10 13 + +/* YUV Planar */ +#define SUN6I_CSI_INPUT_YUV_SEQ_YUYV 0 +#define SUN6I_CSI_INPUT_YUV_SEQ_YVYU 1 +#define SUN6I_CSI_INPUT_YUV_SEQ_UYVY 2 +#define SUN6I_CSI_INPUT_YUV_SEQ_VYUY 3 + +/* YUV Semi-planar */ +#define SUN6I_CSI_INPUT_YUV_SEQ_UV 0 +#define SUN6I_CSI_INPUT_YUV_SEQ_VU 1 + +#define SUN6I_CSI_CH_SCALE_REG 0x4c +#define SUN6I_CSI_CH_SCALE_QUART_EN BIT(0) + +#define SUN6I_CSI_CH_FIFO0_ADDR_REG 0x50 +#define SUN6I_CSI_CH_FIFO1_ADDR_REG 0x58 +#define SUN6I_CSI_CH_FIFO2_ADDR_REG 0x60 + +#define SUN6I_CSI_CH_STA_REG 0x6c +#define SUN6I_CSI_CH_STA_FIELD BIT(2) +#define SUN6I_CSI_CH_STA_VCAP BIT(1) +#define SUN6I_CSI_CH_STA_SCAP BIT(0) + +#define SUN6I_CSI_CH_INT_EN_REG 0x70 +#define SUN6I_CSI_CH_INT_EN_VS BIT(7) +#define SUN6I_CSI_CH_INT_EN_HB_OF BIT(6) +#define SUN6I_CSI_CH_INT_EN_MUL_ERR BIT(5) +#define SUN6I_CSI_CH_INT_EN_FIFO2_OF BIT(4) +#define SUN6I_CSI_CH_INT_EN_FIFO1_OF BIT(3) +#define SUN6I_CSI_CH_INT_EN_FIFO0_OF BIT(2) +#define SUN6I_CSI_CH_INT_EN_FD BIT(1) +#define SUN6I_CSI_CH_INT_EN_CD BIT(0) + +#define SUN6I_CSI_CH_INT_STA_REG 0x74 +#define SUN6I_CSI_CH_INT_STA_CLEAR 0xff +#define SUN6I_CSI_CH_INT_STA_VS BIT(7) +#define SUN6I_CSI_CH_INT_STA_HB_OF BIT(6) +#define SUN6I_CSI_CH_INT_STA_MUL_ERR BIT(5) +#define SUN6I_CSI_CH_INT_STA_FIFO2_OF BIT(4) +#define SUN6I_CSI_CH_INT_STA_FIFO1_OF BIT(3) +#define SUN6I_CSI_CH_INT_STA_FIFO0_OF BIT(2) +#define SUN6I_CSI_CH_INT_STA_FD BIT(1) +#define SUN6I_CSI_CH_INT_STA_CD BIT(0) + +#define SUN6I_CSI_CH_FLD1_VSIZE_REG 0x78 +#define SUN6I_CSI_CH_FLD1_VSIZE_VER_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_FLD1_VSIZE_VER_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_HSIZE_REG 0x80 +#define SUN6I_CSI_CH_HSIZE_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_HSIZE_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_VSIZE_REG 0x84 +#define SUN6I_CSI_CH_VSIZE_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_VSIZE_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_BUF_LEN_REG 0x88 +#define SUN6I_CSI_CH_BUF_LEN_CHROMA_LINE(v) (((v) << 16) & GENMASK(29, 16)) +#define SUN6I_CSI_CH_BUF_LEN_LUMA_LINE(v) ((v) & GENMASK(13, 0)) + +#define SUN6I_CSI_CH_FLIP_SIZE_REG 0x8c +#define SUN6I_CSI_CH_FLIP_SIZE_VER_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_FLIP_SIZE_VALID_LEN(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_FRM_CLK_CNT_REG 0x90 +#define SUN6I_CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94 +#define SUN6I_CSI_CH_FIFO_STAT_REG 0x98 +#define SUN6I_CSI_CH_PCLK_STAT_REG 0x9c + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c deleted file mode 100644 index 791583d23a65..000000000000 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c +++ /dev/null @@ -1,733 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. - * Author: Yong Deng <yong.deng@magewell.com> - */ - -#include <linux/of.h> - -#include <media/v4l2-device.h> -#include <media/v4l2-event.h> -#include <media/v4l2-ioctl.h> -#include <media/v4l2-mc.h> -#include <media/videobuf2-dma-contig.h> -#include <media/videobuf2-v4l2.h> - -#include "sun6i_csi.h" -#include "sun6i_video.h" - -/* This is got from BSP sources. */ -#define MIN_WIDTH (32) -#define MIN_HEIGHT (32) -#define MAX_WIDTH (4800) -#define MAX_HEIGHT (4800) - -/* Helpers */ - -static struct v4l2_subdev * -sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad) -{ - struct media_pad *remote; - - remote = media_pad_remote_pad_first(&video->pad); - - if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) - return NULL; - - if (pad) - *pad = remote->index; - - return media_entity_to_v4l2_subdev(remote->entity); -} - -/* Format */ - -static const u32 sun6i_video_formats[] = { - V4L2_PIX_FMT_SBGGR8, - V4L2_PIX_FMT_SGBRG8, - V4L2_PIX_FMT_SGRBG8, - V4L2_PIX_FMT_SRGGB8, - V4L2_PIX_FMT_SBGGR10, - V4L2_PIX_FMT_SGBRG10, - V4L2_PIX_FMT_SGRBG10, - V4L2_PIX_FMT_SRGGB10, - V4L2_PIX_FMT_SBGGR12, - V4L2_PIX_FMT_SGBRG12, - V4L2_PIX_FMT_SGRBG12, - V4L2_PIX_FMT_SRGGB12, - V4L2_PIX_FMT_YUYV, - V4L2_PIX_FMT_YVYU, - V4L2_PIX_FMT_UYVY, - V4L2_PIX_FMT_VYUY, - V4L2_PIX_FMT_NV12_16L16, - V4L2_PIX_FMT_NV12, - V4L2_PIX_FMT_NV21, - V4L2_PIX_FMT_YUV420, - V4L2_PIX_FMT_YVU420, - V4L2_PIX_FMT_NV16, - V4L2_PIX_FMT_NV61, - V4L2_PIX_FMT_YUV422P, - V4L2_PIX_FMT_RGB565, - V4L2_PIX_FMT_RGB565X, - V4L2_PIX_FMT_JPEG, -}; - -static bool sun6i_video_format_check(u32 format) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(sun6i_video_formats); i++) - if (sun6i_video_formats[i] == format) - return true; - - return false; -} - -/* Video */ - -static void sun6i_video_buffer_configure(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_buffer *csi_buffer) -{ - csi_buffer->queued_to_csi = true; - sun6i_csi_update_buf_addr(csi_dev, csi_buffer->dma_addr); -} - -static void sun6i_video_configure(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct sun6i_csi_config config = { 0 }; - - config.pixelformat = video->format.fmt.pix.pixelformat; - config.code = video->mbus_code; - config.field = video->format.fmt.pix.field; - config.width = video->format.fmt.pix.width; - config.height = video->format.fmt.pix.height; - - sun6i_csi_update_config(csi_dev, &config); -} - -/* Queue */ - -static int sun6i_video_queue_setup(struct vb2_queue *queue, - unsigned int *buffers_count, - unsigned int *planes_count, - unsigned int sizes[], - struct device *alloc_devs[]) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - unsigned int size = video->format.fmt.pix.sizeimage; - - if (*planes_count) - return sizes[0] < size ? -EINVAL : 0; - - *planes_count = 1; - sizes[0] = size; - - return 0; -} - -static int sun6i_video_buffer_prepare(struct vb2_buffer *buffer) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; - struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); - struct sun6i_csi_buffer *csi_buffer = - container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); - unsigned long size = video->format.fmt.pix.sizeimage; - - if (vb2_plane_size(buffer, 0) < size) { - v4l2_err(v4l2_dev, "buffer too small (%lu < %lu)\n", - vb2_plane_size(buffer, 0), size); - return -EINVAL; - } - - vb2_set_plane_payload(buffer, 0, size); - - csi_buffer->dma_addr = vb2_dma_contig_plane_dma_addr(buffer, 0); - v4l2_buffer->field = video->format.fmt.pix.field; - - return 0; -} - -static void sun6i_video_buffer_queue(struct vb2_buffer *buffer) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); - struct sun6i_video *video = &csi_dev->video; - struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); - struct sun6i_csi_buffer *csi_buffer = - container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); - unsigned long flags; - - spin_lock_irqsave(&video->dma_queue_lock, flags); - csi_buffer->queued_to_csi = false; - list_add_tail(&csi_buffer->list, &video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); -} - -static int sun6i_video_start_streaming(struct vb2_queue *queue, - unsigned int count) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - struct video_device *video_dev = &video->video_dev; - struct sun6i_csi_buffer *buf; - struct sun6i_csi_buffer *next_buf; - struct v4l2_subdev *subdev; - unsigned long flags; - int ret; - - video->sequence = 0; - - ret = video_device_pipeline_alloc_start(video_dev); - if (ret < 0) - goto error_dma_queue_flush; - - if (video->mbus_code == 0) { - ret = -EINVAL; - goto error_media_pipeline; - } - - subdev = sun6i_video_remote_subdev(video, NULL); - if (!subdev) { - ret = -EINVAL; - goto error_media_pipeline; - } - - sun6i_video_configure(csi_dev); - - spin_lock_irqsave(&video->dma_queue_lock, flags); - - buf = list_first_entry(&video->dma_queue, - struct sun6i_csi_buffer, list); - sun6i_video_buffer_configure(csi_dev, buf); - - sun6i_csi_set_stream(csi_dev, true); - - /* - * CSI will lookup the next dma buffer for next frame before the - * current frame done IRQ triggered. This is not documented - * but reported by Ondřej Jirman. - * The BSP code has workaround for this too. It skip to mark the - * first buffer as frame done for VB2 and pass the second buffer - * to CSI in the first frame done ISR call. Then in second frame - * done ISR call, it mark the first buffer as frame done for VB2 - * and pass the third buffer to CSI. And so on. The bad thing is - * that the first buffer will be written twice and the first frame - * is dropped even the queued buffer is sufficient. - * So, I make some improvement here. Pass the next buffer to CSI - * just follow starting the CSI. In this case, the first frame - * will be stored in first buffer, second frame in second buffer. - * This method is used to avoid dropping the first frame, it - * would also drop frame when lacking of queued buffer. - */ - next_buf = list_next_entry(buf, list); - sun6i_video_buffer_configure(csi_dev, next_buf); - - spin_unlock_irqrestore(&video->dma_queue_lock, flags); - - ret = v4l2_subdev_call(subdev, video, s_stream, 1); - if (ret && ret != -ENOIOCTLCMD) - goto error_stream; - - return 0; - -error_stream: - sun6i_csi_set_stream(csi_dev, false); - -error_media_pipeline: - video_device_pipeline_stop(video_dev); - -error_dma_queue_flush: - spin_lock_irqsave(&video->dma_queue_lock, flags); - list_for_each_entry(buf, &video->dma_queue, list) - vb2_buffer_done(&buf->v4l2_buffer.vb2_buf, - VB2_BUF_STATE_QUEUED); - INIT_LIST_HEAD(&video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); - - return ret; -} - -static void sun6i_video_stop_streaming(struct vb2_queue *queue) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_subdev *subdev; - unsigned long flags; - struct sun6i_csi_buffer *buf; - - subdev = sun6i_video_remote_subdev(video, NULL); - if (subdev) - v4l2_subdev_call(subdev, video, s_stream, 0); - - sun6i_csi_set_stream(csi_dev, false); - - video_device_pipeline_stop(&video->video_dev); - - /* Release all active buffers */ - spin_lock_irqsave(&video->dma_queue_lock, flags); - list_for_each_entry(buf, &video->dma_queue, list) - vb2_buffer_done(&buf->v4l2_buffer.vb2_buf, VB2_BUF_STATE_ERROR); - INIT_LIST_HEAD(&video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); -} - -void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct sun6i_csi_buffer *buf; - struct sun6i_csi_buffer *next_buf; - struct vb2_v4l2_buffer *v4l2_buffer; - - spin_lock(&video->dma_queue_lock); - - buf = list_first_entry(&video->dma_queue, - struct sun6i_csi_buffer, list); - if (list_is_last(&buf->list, &video->dma_queue)) { - dev_dbg(csi_dev->dev, "Frame dropped!\n"); - goto complete; - } - - next_buf = list_next_entry(buf, list); - /* If a new buffer (#next_buf) had not been queued to CSI, the old - * buffer (#buf) is still holding by CSI for storing the next - * frame. So, we queue a new buffer (#next_buf) to CSI then wait - * for next ISR call. - */ - if (!next_buf->queued_to_csi) { - sun6i_video_buffer_configure(csi_dev, next_buf); - dev_dbg(csi_dev->dev, "Frame dropped!\n"); - goto complete; - } - - list_del(&buf->list); - v4l2_buffer = &buf->v4l2_buffer; - v4l2_buffer->vb2_buf.timestamp = ktime_get_ns(); - v4l2_buffer->sequence = video->sequence; - vb2_buffer_done(&v4l2_buffer->vb2_buf, VB2_BUF_STATE_DONE); - - /* Prepare buffer for next frame but one. */ - if (!list_is_last(&next_buf->list, &video->dma_queue)) { - next_buf = list_next_entry(next_buf, list); - sun6i_video_buffer_configure(csi_dev, next_buf); - } else { - dev_dbg(csi_dev->dev, "Next frame will be dropped!\n"); - } - -complete: - video->sequence++; - spin_unlock(&video->dma_queue_lock); -} - -static const struct vb2_ops sun6i_video_queue_ops = { - .queue_setup = sun6i_video_queue_setup, - .buf_prepare = sun6i_video_buffer_prepare, - .buf_queue = sun6i_video_buffer_queue, - .start_streaming = sun6i_video_start_streaming, - .stop_streaming = sun6i_video_stop_streaming, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, -}; - -/* V4L2 Device */ - -static int sun6i_video_querycap(struct file *file, void *private, - struct v4l2_capability *capability) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct video_device *video_dev = &csi_dev->video.video_dev; - - strscpy(capability->driver, SUN6I_CSI_NAME, sizeof(capability->driver)); - strscpy(capability->card, video_dev->name, sizeof(capability->card)); - snprintf(capability->bus_info, sizeof(capability->bus_info), - "platform:%s", dev_name(csi_dev->dev)); - - return 0; -} - -static int sun6i_video_enum_fmt(struct file *file, void *private, - struct v4l2_fmtdesc *fmtdesc) -{ - u32 index = fmtdesc->index; - - if (index >= ARRAY_SIZE(sun6i_video_formats)) - return -EINVAL; - - fmtdesc->pixelformat = sun6i_video_formats[index]; - - return 0; -} - -static int sun6i_video_g_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - *format = video->format; - - return 0; -} - -static int sun6i_video_format_try(struct sun6i_video *video, - struct v4l2_format *format) -{ - struct v4l2_pix_format *pix_format = &format->fmt.pix; - int bpp; - - if (!sun6i_video_format_check(pix_format->pixelformat)) - pix_format->pixelformat = sun6i_video_formats[0]; - - v4l_bound_align_image(&pix_format->width, MIN_WIDTH, MAX_WIDTH, 1, - &pix_format->height, MIN_HEIGHT, MAX_WIDTH, 1, 1); - - bpp = sun6i_csi_get_bpp(pix_format->pixelformat); - pix_format->bytesperline = (pix_format->width * bpp) >> 3; - pix_format->sizeimage = pix_format->bytesperline * pix_format->height; - - if (pix_format->field == V4L2_FIELD_ANY) - pix_format->field = V4L2_FIELD_NONE; - - if (pix_format->pixelformat == V4L2_PIX_FMT_JPEG) - pix_format->colorspace = V4L2_COLORSPACE_JPEG; - else - pix_format->colorspace = V4L2_COLORSPACE_SRGB; - - pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; - pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; - pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; - - return 0; -} - -static int sun6i_video_format_set(struct sun6i_video *video, - struct v4l2_format *format) -{ - int ret; - - ret = sun6i_video_format_try(video, format); - if (ret) - return ret; - - video->format = *format; - - return 0; -} - -static int sun6i_video_s_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - if (vb2_is_busy(&video->queue)) - return -EBUSY; - - return sun6i_video_format_set(video, format); -} - -static int sun6i_video_try_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - return sun6i_video_format_try(video, format); -} - -static int sun6i_video_enum_input(struct file *file, void *private, - struct v4l2_input *input) -{ - if (input->index != 0) - return -EINVAL; - - input->type = V4L2_INPUT_TYPE_CAMERA; - strscpy(input->name, "Camera", sizeof(input->name)); - - return 0; -} - -static int sun6i_video_g_input(struct file *file, void *private, - unsigned int *index) -{ - *index = 0; - - return 0; -} - -static int sun6i_video_s_input(struct file *file, void *private, - unsigned int index) -{ - if (index != 0) - return -EINVAL; - - return 0; -} - -static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = { - .vidioc_querycap = sun6i_video_querycap, - - .vidioc_enum_fmt_vid_cap = sun6i_video_enum_fmt, - .vidioc_g_fmt_vid_cap = sun6i_video_g_fmt, - .vidioc_s_fmt_vid_cap = sun6i_video_s_fmt, - .vidioc_try_fmt_vid_cap = sun6i_video_try_fmt, - - .vidioc_enum_input = sun6i_video_enum_input, - .vidioc_g_input = sun6i_video_g_input, - .vidioc_s_input = sun6i_video_s_input, - - .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_reqbufs = vb2_ioctl_reqbufs, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_expbuf = vb2_ioctl_expbuf, - .vidioc_qbuf = vb2_ioctl_qbuf, - .vidioc_dqbuf = vb2_ioctl_dqbuf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, -}; - -/* V4L2 File */ - -static int sun6i_video_open(struct file *file) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - int ret = 0; - - if (mutex_lock_interruptible(&video->lock)) - return -ERESTARTSYS; - - ret = v4l2_fh_open(file); - if (ret < 0) - goto error_lock; - - ret = v4l2_pipeline_pm_get(&video->video_dev.entity); - if (ret < 0) - goto error_v4l2_fh; - - /* Power on at first open. */ - if (v4l2_fh_is_singular_file(file)) { - ret = sun6i_csi_set_power(csi_dev, true); - if (ret < 0) - goto error_v4l2_fh; - } - - mutex_unlock(&video->lock); - - return 0; - -error_v4l2_fh: - v4l2_fh_release(file); - -error_lock: - mutex_unlock(&video->lock); - - return ret; -} - -static int sun6i_video_close(struct file *file) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - bool last_close; - - mutex_lock(&video->lock); - - last_close = v4l2_fh_is_singular_file(file); - - _vb2_fop_release(file, NULL); - v4l2_pipeline_pm_put(&video->video_dev.entity); - - /* Power off at last close. */ - if (last_close) - sun6i_csi_set_power(csi_dev, false); - - mutex_unlock(&video->lock); - - return 0; -} - -static const struct v4l2_file_operations sun6i_video_fops = { - .owner = THIS_MODULE, - .open = sun6i_video_open, - .release = sun6i_video_close, - .unlocked_ioctl = video_ioctl2, - .mmap = vb2_fop_mmap, - .poll = vb2_fop_poll -}; - -/* Media Entity */ - -static int sun6i_video_link_validate_get_format(struct media_pad *pad, - struct v4l2_subdev_format *fmt) -{ - if (is_media_entity_v4l2_subdev(pad->entity)) { - struct v4l2_subdev *sd = - media_entity_to_v4l2_subdev(pad->entity); - - fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; - fmt->pad = pad->index; - return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); - } - - return -EINVAL; -} - -static int sun6i_video_link_validate(struct media_link *link) -{ - struct video_device *vdev = container_of(link->sink->entity, - struct video_device, entity); - struct sun6i_csi_device *csi_dev = video_get_drvdata(vdev); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_subdev_format source_fmt; - int ret; - - video->mbus_code = 0; - - if (!media_pad_remote_pad_first(link->sink->entity->pads)) { - dev_info(csi_dev->dev, "video node %s pad not connected\n", - vdev->name); - return -ENOLINK; - } - - ret = sun6i_video_link_validate_get_format(link->source, &source_fmt); - if (ret < 0) - return ret; - - if (!sun6i_csi_is_format_supported(csi_dev, - video->format.fmt.pix.pixelformat, - source_fmt.format.code)) { - dev_err(csi_dev->dev, - "Unsupported pixformat: 0x%x with mbus code: 0x%x!\n", - video->format.fmt.pix.pixelformat, - source_fmt.format.code); - return -EPIPE; - } - - if (source_fmt.format.width != video->format.fmt.pix.width || - source_fmt.format.height != video->format.fmt.pix.height) { - dev_err(csi_dev->dev, - "Wrong width or height %ux%u (%ux%u expected)\n", - video->format.fmt.pix.width, video->format.fmt.pix.height, - source_fmt.format.width, source_fmt.format.height); - return -EPIPE; - } - - video->mbus_code = source_fmt.format.code; - - return 0; -} - -static const struct media_entity_operations sun6i_video_media_ops = { - .link_validate = sun6i_video_link_validate -}; - -/* Video */ - -int sun6i_video_setup(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; - struct video_device *video_dev = &video->video_dev; - struct vb2_queue *queue = &video->queue; - struct media_pad *pad = &video->pad; - struct v4l2_format format = { 0 }; - struct v4l2_pix_format *pix_format = &format.fmt.pix; - int ret; - - /* Media Entity */ - - video_dev->entity.ops = &sun6i_video_media_ops; - - /* Media Pad */ - - pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; - - ret = media_entity_pads_init(&video_dev->entity, 1, pad); - if (ret < 0) - return ret; - - /* DMA queue */ - - INIT_LIST_HEAD(&video->dma_queue); - spin_lock_init(&video->dma_queue_lock); - - video->sequence = 0; - - /* Queue */ - - mutex_init(&video->lock); - - queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - queue->io_modes = VB2_MMAP | VB2_DMABUF; - queue->buf_struct_size = sizeof(struct sun6i_csi_buffer); - queue->ops = &sun6i_video_queue_ops; - queue->mem_ops = &vb2_dma_contig_memops; - queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - queue->lock = &video->lock; - queue->dev = csi_dev->dev; - queue->drv_priv = csi_dev; - - /* Make sure non-dropped frame. */ - queue->min_buffers_needed = 3; - - ret = vb2_queue_init(queue); - if (ret) { - v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); - goto error_media_entity; - } - - /* V4L2 Format */ - - format.type = queue->type; - pix_format->pixelformat = sun6i_video_formats[0]; - pix_format->width = 1280; - pix_format->height = 720; - pix_format->field = V4L2_FIELD_NONE; - - sun6i_video_format_set(video, &format); - - /* Video Device */ - - strscpy(video_dev->name, SUN6I_CSI_NAME, sizeof(video_dev->name)); - video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - video_dev->vfl_dir = VFL_DIR_RX; - video_dev->release = video_device_release_empty; - video_dev->fops = &sun6i_video_fops; - video_dev->ioctl_ops = &sun6i_video_ioctl_ops; - video_dev->v4l2_dev = v4l2_dev; - video_dev->queue = queue; - video_dev->lock = &video->lock; - - video_set_drvdata(video_dev, csi_dev); - - ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); - if (ret < 0) { - v4l2_err(v4l2_dev, "failed to register video device: %d\n", - ret); - goto error_media_entity; - } - - return 0; - -error_media_entity: - media_entity_cleanup(&video_dev->entity); - - mutex_destroy(&video->lock); - - return ret; -} - -void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct video_device *video_dev = &video->video_dev; - - vb2_video_unregister_device(video_dev); - media_entity_cleanup(&video_dev->entity); - mutex_destroy(&video->lock); -} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h deleted file mode 100644 index a917d2da6deb..000000000000 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. - * Author: Yong Deng <yong.deng@magewell.com> - */ - -#ifndef __SUN6I_VIDEO_H__ -#define __SUN6I_VIDEO_H__ - -#include <media/v4l2-dev.h> -#include <media/videobuf2-core.h> - -struct sun6i_csi_device; - -struct sun6i_video { - struct video_device video_dev; - struct vb2_queue queue; - struct mutex lock; /* Queue lock. */ - struct media_pad pad; - - struct list_head dma_queue; - spinlock_t dma_queue_lock; /* DMA queue lock. */ - - struct v4l2_format format; - u32 mbus_code; - unsigned int sequence; -}; - -int sun6i_video_setup(struct sun6i_csi_device *csi_dev); -void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev); - -void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev); - -#endif /* __SUN6I_VIDEO_H__ */ diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c index 30d6c0c5161f..484ac5f054d5 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c @@ -498,6 +498,7 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) struct v4l2_async_notifier *notifier = &bridge->notifier; struct media_pad *pads = bridge->pads; struct device *dev = csi2_dev->dev; + bool notifier_registered = false; int ret; mutex_init(&bridge->lock); @@ -519,8 +520,10 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) /* Media Pads */ - pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; ret = media_entity_pads_init(&subdev->entity, SUN6I_MIPI_CSI2_PAD_COUNT, pads); @@ -533,12 +536,17 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) notifier->ops = &sun6i_mipi_csi2_notifier_ops; ret = sun6i_mipi_csi2_bridge_source_setup(csi2_dev); - if (ret) + if (ret && ret != -ENODEV) goto error_v4l2_notifier_cleanup; - ret = v4l2_async_subdev_nf_register(subdev, notifier); - if (ret < 0) - goto error_v4l2_notifier_cleanup; + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } /* V4L2 Subdev */ @@ -549,7 +557,8 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) return 0; error_v4l2_notifier_unregister: - v4l2_async_nf_unregister(notifier); + if (notifier_registered) + v4l2_async_nf_unregister(notifier); error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c index b032ec13a683..d993c09a4820 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c @@ -536,6 +536,7 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) struct v4l2_async_notifier *notifier = &bridge->notifier; struct media_pad *pads = bridge->pads; struct device *dev = csi2_dev->dev; + bool notifier_registered = false; int ret; mutex_init(&bridge->lock); @@ -557,8 +558,10 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) /* Media Pads */ - pads[SUN8I_A83T_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[SUN8I_A83T_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[SUN8I_A83T_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN8I_A83T_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; ret = media_entity_pads_init(&subdev->entity, SUN8I_A83T_MIPI_CSI2_PAD_COUNT, pads); @@ -571,12 +574,17 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) notifier->ops = &sun8i_a83t_mipi_csi2_notifier_ops; ret = sun8i_a83t_mipi_csi2_bridge_source_setup(csi2_dev); - if (ret) + if (ret && ret != -ENODEV) goto error_v4l2_notifier_cleanup; - ret = v4l2_async_subdev_nf_register(subdev, notifier); - if (ret < 0) - goto error_v4l2_notifier_cleanup; + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } /* V4L2 Subdev */ @@ -587,7 +595,8 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) return 0; error_v4l2_notifier_unregister: - v4l2_async_nf_unregister(notifier); + if (notifier_registered) + v4l2_async_nf_unregister(notifier); error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); diff --git a/drivers/media/platform/ti/omap3isp/isp.c b/drivers/media/platform/ti/omap3isp/isp.c index 24d2383400b0..1d40bb59ff81 100644 --- a/drivers/media/platform/ti/omap3isp/isp.c +++ b/drivers/media/platform/ti/omap3isp/isp.c @@ -1884,8 +1884,7 @@ static int isp_initialize_modules(struct isp_device *isp) ret = omap3isp_ccp2_init(isp); if (ret < 0) { - if (ret != -EPROBE_DEFER) - dev_err(isp->dev, "CCP2 initialization failed\n"); + dev_err_probe(isp->dev, ret, "CCP2 initialization failed\n"); goto error_ccp2; } diff --git a/drivers/media/platform/xilinx/xilinx-csi2rxss.c b/drivers/media/platform/xilinx/xilinx-csi2rxss.c index 29b53febc2e7..d8a23f18cfbc 100644 --- a/drivers/media/platform/xilinx/xilinx-csi2rxss.c +++ b/drivers/media/platform/xilinx/xilinx-csi2rxss.c @@ -976,11 +976,9 @@ static int xcsi2rxss_probe(struct platform_device *pdev) /* Reset GPIO */ xcsi2rxss->rst_gpio = devm_gpiod_get_optional(dev, "video-reset", GPIOD_OUT_HIGH); - if (IS_ERR(xcsi2rxss->rst_gpio)) { - if (PTR_ERR(xcsi2rxss->rst_gpio) != -EPROBE_DEFER) - dev_err(dev, "Video Reset GPIO not setup in DT"); - return PTR_ERR(xcsi2rxss->rst_gpio); - } + if (IS_ERR(xcsi2rxss->rst_gpio)) + return dev_err_probe(dev, PTR_ERR(xcsi2rxss->rst_gpio), + "Video Reset GPIO not setup in DT\n"); ret = xcsi2rxss_parse_of(xcsi2rxss); if (ret < 0) diff --git a/drivers/media/radio/radio-tea5764.c b/drivers/media/radio/radio-tea5764.c index abda40e81612..2cb74afba49c 100644 --- a/drivers/media/radio/radio-tea5764.c +++ b/drivers/media/radio/radio-tea5764.c @@ -411,8 +411,7 @@ static const struct video_device tea5764_radio_template = { }; /* I2C probe: check if the device exists and register with v4l if it is */ -static int tea5764_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tea5764_i2c_probe(struct i2c_client *client) { struct tea5764_device *radio; struct v4l2_device *v4l2_dev; @@ -512,7 +511,7 @@ static struct i2c_driver tea5764_i2c_driver = { .driver = { .name = "radio-tea5764", }, - .probe = tea5764_i2c_probe, + .probe_new = tea5764_i2c_probe, .remove = tea5764_i2c_remove, .id_table = tea5764_id, }; diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c index 8b8ce2b46a55..621bb8523271 100644 --- a/drivers/media/radio/radio-terratec.c +++ b/drivers/media/radio/radio-terratec.c @@ -82,7 +82,6 @@ static int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) { int i; - int p; int temp; long rest; unsigned char buffer[25]; /* we have to bit shift 25 registers */ @@ -93,7 +92,6 @@ static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) rest = freq * 10 + 10700; /* I once had understood what is going on here */ /* maybe some wise guy (friedhelm?) can comment this stuff */ i = 13; - p = 10; temp = 102400; while (rest != 0) { if (rest % temp == rest) @@ -103,7 +101,6 @@ static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) rest = rest - temp; } i--; - p--; temp = temp / 2; } diff --git a/drivers/media/radio/saa7706h.c b/drivers/media/radio/saa7706h.c index f9e990a9c3ef..3c758a983344 100644 --- a/drivers/media/radio/saa7706h.c +++ b/drivers/media/radio/saa7706h.c @@ -331,8 +331,7 @@ static const struct v4l2_subdev_ops empty_ops = {}; * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int saa7706h_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7706h_probe(struct i2c_client *client) { struct saa7706h_state *state; struct v4l2_subdev *sd; @@ -406,7 +405,7 @@ static struct i2c_driver saa7706h_driver = { .driver = { .name = DRIVER_NAME, }, - .probe = saa7706h_probe, + .probe_new = saa7706h_probe, .remove = saa7706h_remove, .id_table = saa7706h_id, }; diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c index 6b2768623c88..aa7a580dbecc 100644 --- a/drivers/media/radio/si470x/radio-si470x-usb.c +++ b/drivers/media/radio/si470x/radio-si470x-usb.c @@ -727,8 +727,10 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, /* start radio */ retval = si470x_start_usb(radio); - if (retval < 0) + if (retval < 0 && !radio->int_in_running) goto err_buf; + else if (retval < 0) /* in case of radio->int_in_running == 1 */ + goto err_all; /* set initial frequency */ si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */ diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c index 7b0870a9785b..d14c97d79e83 100644 --- a/drivers/media/radio/tef6862.c +++ b/drivers/media/radio/tef6862.c @@ -141,8 +141,7 @@ static const struct v4l2_subdev_ops tef6862_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int tef6862_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tef6862_probe(struct i2c_client *client) { struct tef6862_state *state; struct v4l2_subdev *sd; @@ -184,7 +183,7 @@ static struct i2c_driver tef6862_driver = { .driver = { .name = DRIVER_NAME, }, - .probe = tef6862_probe, + .probe_new = tef6862_probe, .remove = tef6862_remove, .id_table = tef6862_id, }; diff --git a/drivers/media/rc/gpio-ir-recv.c b/drivers/media/rc/gpio-ir-recv.c index 22e524b69806..8f1fff7af6c9 100644 --- a/drivers/media/rc/gpio-ir-recv.c +++ b/drivers/media/rc/gpio-ir-recv.c @@ -74,13 +74,9 @@ static int gpio_ir_recv_probe(struct platform_device *pdev) return -ENOMEM; gpio_dev->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN); - if (IS_ERR(gpio_dev->gpiod)) { - rc = PTR_ERR(gpio_dev->gpiod); - /* Just try again if this happens */ - if (rc != -EPROBE_DEFER) - dev_err(dev, "error getting gpio (%d)\n", rc); - return rc; - } + if (IS_ERR(gpio_dev->gpiod)) + return dev_err_probe(dev, PTR_ERR(gpio_dev->gpiod), + "error getting gpio\n"); gpio_dev->irq = gpiod_to_irq(gpio_dev->gpiod); if (gpio_dev->irq < 0) return gpio_dev->irq; diff --git a/drivers/media/rc/gpio-ir-tx.c b/drivers/media/rc/gpio-ir-tx.c index d3063ddb472e..2b829c146db1 100644 --- a/drivers/media/rc/gpio-ir-tx.c +++ b/drivers/media/rc/gpio-ir-tx.c @@ -174,12 +174,9 @@ static int gpio_ir_tx_probe(struct platform_device *pdev) return -ENOMEM; gpio_ir->gpio = devm_gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW); - if (IS_ERR(gpio_ir->gpio)) { - if (PTR_ERR(gpio_ir->gpio) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Failed to get gpio (%ld)\n", - PTR_ERR(gpio_ir->gpio)); - return PTR_ERR(gpio_ir->gpio); - } + if (IS_ERR(gpio_ir->gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(gpio_ir->gpio), + "Failed to get gpio\n"); rcdev->priv = gpio_ir; rcdev->driver_name = DRIVER_NAME; diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c index 5edfd8a9e849..74546f7e3469 100644 --- a/drivers/media/rc/imon.c +++ b/drivers/media/rc/imon.c @@ -646,15 +646,14 @@ static int send_packet(struct imon_context *ictx) pr_err_ratelimited("error submitting urb(%d)\n", retval); } else { /* Wait for transmission to complete (or abort) */ - mutex_unlock(&ictx->lock); retval = wait_for_completion_interruptible( &ictx->tx.finished); if (retval) { usb_kill_urb(ictx->tx_urb); pr_err_ratelimited("task interrupted\n"); } - mutex_lock(&ictx->lock); + ictx->tx.busy = false; retval = ictx->tx.status; if (retval) pr_err_ratelimited("packet tx failed (%d)\n", retval); @@ -953,7 +952,8 @@ static ssize_t vfd_write(struct file *file, const char __user *buf, if (ictx->disconnected) return -ENODEV; - mutex_lock(&ictx->lock); + if (mutex_lock_interruptible(&ictx->lock)) + return -ERESTARTSYS; if (!ictx->dev_present_intf0) { pr_err_ratelimited("no iMON device present\n"); diff --git a/drivers/media/rc/ir-rx51.c b/drivers/media/rc/ir-rx51.c index a3b145183260..85080c3d2053 100644 --- a/drivers/media/rc/ir-rx51.c +++ b/drivers/media/rc/ir-rx51.c @@ -231,13 +231,8 @@ static int ir_rx51_probe(struct platform_device *dev) struct rc_dev *rcdev; pwm = pwm_get(&dev->dev, NULL); - if (IS_ERR(pwm)) { - int err = PTR_ERR(pwm); - - if (err != -EPROBE_DEFER) - dev_err(&dev->dev, "pwm_get failed: %d\n", err); - return err; - } + if (IS_ERR(pwm)) + return dev_err_probe(&dev->dev, PTR_ERR(pwm), "pwm_get failed\n"); /* Use default, in case userspace does not set the carrier */ ir_rx51.freq = DIV_ROUND_CLOSEST_ULL(pwm_get_period(pwm), NSEC_PER_SEC); diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index 51aa55a84bb5..bbc81bed4f90 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -158,8 +158,15 @@ static const struct of_device_id ir_spi_of_match[] = { }; MODULE_DEVICE_TABLE(of, ir_spi_of_match); +static const struct spi_device_id ir_spi_ids[] = { + { "ir-spi-led" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, ir_spi_ids); + static struct spi_driver ir_spi_driver = { .probe = ir_spi_probe, + .id_table = ir_spi_ids, .driver = { .name = IR_SPI_DRIVER_NAME, .of_match_table = ir_spi_of_match, diff --git a/drivers/media/test-drivers/Kconfig b/drivers/media/test-drivers/Kconfig index 51cf27834df0..459b433e9fae 100644 --- a/drivers/media/test-drivers/Kconfig +++ b/drivers/media/test-drivers/Kconfig @@ -20,6 +20,7 @@ config VIDEO_VIM2M source "drivers/media/test-drivers/vicodec/Kconfig" source "drivers/media/test-drivers/vimc/Kconfig" source "drivers/media/test-drivers/vivid/Kconfig" +source "drivers/media/test-drivers/visl/Kconfig" endif #V4L_TEST_DRIVERS diff --git a/drivers/media/test-drivers/Makefile b/drivers/media/test-drivers/Makefile index ff390b687189..740714a4584d 100644 --- a/drivers/media/test-drivers/Makefile +++ b/drivers/media/test-drivers/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_VIDEO_VICODEC) += vicodec/ obj-$(CONFIG_VIDEO_VIM2M) += vim2m.o obj-$(CONFIG_VIDEO_VIMC) += vimc/ obj-$(CONFIG_VIDEO_VIVID) += vivid/ +obj-$(CONFIG_VIDEO_VISL) += visl/ diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c index 82620613d56b..dff7265a42ca 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -459,26 +459,20 @@ fail_dmx_conn: for (j = j - 1; j >= 0; --j) dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->dmx_fe[j]); -fail_dmx_dev: dvb_dmxdev_release(&dvb->dmx_dev); -fail_dmx: +fail_dmx_dev: dvb_dmx_release(&dvb->demux); +fail_dmx: +fail_demod_probe: + for (i = i - 1; i >= 0; --i) { + dvb_unregister_frontend(dvb->fe[i]); fail_fe: - for (j = i; j >= 0; --j) - dvb_unregister_frontend(dvb->fe[j]); + dvb_module_release(dvb->i2c_client_tuner[i]); fail_tuner_probe: - for (j = i; j >= 0; --j) - if (dvb->i2c_client_tuner[j]) - dvb_module_release(dvb->i2c_client_tuner[j]); - -fail_demod_probe: - for (j = i; j >= 0; --j) - if (dvb->i2c_client_demod[j]) - dvb_module_release(dvb->i2c_client_demod[j]); - + dvb_module_release(dvb->i2c_client_demod[i]); + } fail_adapter: dvb_unregister_adapter(&dvb->adapter); - fail_i2c: i2c_del_adapter(&dvb->i2c_adapter); diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c index d60c6d16beea..b878db798686 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_demod.c +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c @@ -412,8 +412,7 @@ static const struct i2c_device_id vidtv_demod_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, vidtv_demod_i2c_id_table); -static int vidtv_demod_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vidtv_demod_i2c_probe(struct i2c_client *client) { struct vidtv_tuner_config *config = client->dev.platform_data; struct vidtv_demod_state *state; @@ -450,7 +449,7 @@ static struct i2c_driver vidtv_demod_i2c_driver = { .name = "dvb_vidtv_demod", .suppress_bind_attrs = true, }, - .probe = vidtv_demod_i2c_probe, + .probe_new = vidtv_demod_i2c_probe, .remove = vidtv_demod_i2c_remove, .id_table = vidtv_demod_i2c_id_table, }; diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.c b/drivers/media/test-drivers/vidtv/vidtv_tuner.c index aabc97ed736b..55a4387f3854 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_tuner.c +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.c @@ -390,8 +390,7 @@ static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table); -static int vidtv_tuner_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vidtv_tuner_i2c_probe(struct i2c_client *client) { struct vidtv_tuner_config *config = client->dev.platform_data; struct dvb_frontend *fe = config->fe; @@ -426,7 +425,7 @@ static struct i2c_driver vidtv_tuner_i2c_driver = { .name = "dvb_vidtv_tuner", .suppress_bind_attrs = true, }, - .probe = vidtv_tuner_i2c_probe, + .probe_new = vidtv_tuner_i2c_probe, .remove = vidtv_tuner_i2c_remove, .id_table = vidtv_tuner_i2c_id_table, }; diff --git a/drivers/media/test-drivers/vimc/vimc-core.c b/drivers/media/test-drivers/vimc/vimc-core.c index 2ae7a0f11ebf..e82cfa5ffbf4 100644 --- a/drivers/media/test-drivers/vimc/vimc-core.c +++ b/drivers/media/test-drivers/vimc/vimc-core.c @@ -433,7 +433,7 @@ static int __init vimc_init(void) if (ret) { dev_err(&vimc_pdev.dev, "platform driver registration failed (err=%d)\n", ret); - platform_driver_unregister(&vimc_pdrv); + platform_device_unregister(&vimc_pdev); return ret; } diff --git a/drivers/media/test-drivers/visl/Kconfig b/drivers/media/test-drivers/visl/Kconfig new file mode 100644 index 000000000000..7508b904f196 --- /dev/null +++ b/drivers/media/test-drivers/visl/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0+ +config VIDEO_VISL + tristate "Virtual Stateless Decoder Driver (visl)" + depends on VIDEO_DEV + select FONT_SUPPORT + select FONT_8x16 + select VIDEOBUF2_VMALLOC + select V4L2_MEM2MEM_DEV + select MEDIA_CONTROLLER + select MEDIA_CONTROLLER_REQUEST_API + select VIDEO_V4L2_TPG + help + + A virtual stateless decoder device for uAPI development purposes. + + A userspace implementation can use visl to run a decoding loop even + when no hardware is available or when the kernel uAPI for the codec + has not been upstreamed yet. This can reveal bugs at an early stage. + + When in doubt, say N. + +config VISL_DEBUGFS + bool "Enable debugfs for visl" + depends on VIDEO_VISL + depends on DEBUG_FS + + help + Choose Y to dump the bitstream buffers through debugfs. + When in doubt, say N. diff --git a/drivers/media/test-drivers/visl/Makefile b/drivers/media/test-drivers/visl/Makefile new file mode 100644 index 000000000000..fb4d5ae1b17f --- /dev/null +++ b/drivers/media/test-drivers/visl/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ +visl-y := visl-core.o visl-video.o visl-dec.o visl-trace-points.o + +ifeq ($(CONFIG_VISL_DEBUGFS),y) + visl-y += visl-debugfs.o +endif + +obj-$(CONFIG_VIDEO_VISL) += visl.o diff --git a/drivers/media/test-drivers/visl/visl-core.c b/drivers/media/test-drivers/visl/visl-core.c new file mode 100644 index 000000000000..9cb60ab653bf --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-core.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A virtual stateless decoder device for stateless uAPI development purposes. + * + * This tool's objective is to help the development and testing of userspace + * applications that use the V4L2 stateless API to decode media. + * + * A userspace implementation can use visl to run a decoding loop even when no + * hardware is available or when the kernel uAPI for the codec has not been + * upstreamed yet. This can reveal bugs at an early stage. + * + * This driver can also trace the contents of the V4L2 controls submitted to it. + * It can also dump the contents of the vb2 buffers through a debugfs + * interface. This is in many ways similar to the tracing infrastructure + * available for other popular encode/decode APIs out there and can help develop + * a userspace application by using another (working) one as a reference. + * + * Note that no actual decoding of video frames is performed by visl. The V4L2 + * test pattern generator is used to write various debug information to the + * capture buffers instead. + * + * Copyright (C) 2022 Collabora, Ltd. + * + * Based on the vim2m driver, that is: + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + * + * Based on the vicodec driver, that is: + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mem2mem.h> + +#include "visl.h" +#include "visl-dec.h" +#include "visl-debugfs.h" +#include "visl-video.h" + +unsigned int visl_debug; +module_param(visl_debug, uint, 0644); +MODULE_PARM_DESC(visl_debug, " activates debug info"); + +unsigned int visl_transtime_ms; +module_param(visl_transtime_ms, uint, 0644); +MODULE_PARM_DESC(visl_transtime_ms, " simulated process time in milliseconds."); + +/* + * dprintk can be slow through serial. This lets one limit the tracing to a + * particular number of frames + */ +int visl_dprintk_frame_start = -1; +module_param(visl_dprintk_frame_start, int, 0); +MODULE_PARM_DESC(visl_dprintk_frame_start, + " a frame number to start tracing with dprintk"); + +unsigned int visl_dprintk_nframes; +module_param(visl_dprintk_nframes, uint, 0); +MODULE_PARM_DESC(visl_dprintk_nframes, + " the number of frames to trace with dprintk"); + +bool keep_bitstream_buffers; +module_param(keep_bitstream_buffers, bool, false); +MODULE_PARM_DESC(keep_bitstream_buffers, + " keep bitstream buffers in debugfs after streaming is stopped"); + +int bitstream_trace_frame_start = -1; +module_param(bitstream_trace_frame_start, int, 0); +MODULE_PARM_DESC(bitstream_trace_frame_start, + " a frame number to start dumping the bitstream through debugfs"); + +unsigned int bitstream_trace_nframes; +module_param(bitstream_trace_nframes, uint, 0); +MODULE_PARM_DESC(bitstream_trace_nframes, + " the number of frames to dump the bitstream through debugfs"); + +static const struct visl_ctrl_desc visl_fwht_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_FWHT_PARAMS, + }, +}; + +const struct visl_ctrls visl_fwht_ctrls = { + .ctrls = visl_fwht_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_fwht_ctrl_descs) +}; + +static const struct visl_ctrl_desc visl_mpeg2_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_MPEG2_SEQUENCE, + }, + { + .cfg.id = V4L2_CID_STATELESS_MPEG2_PICTURE, + }, + { + .cfg.id = V4L2_CID_STATELESS_MPEG2_QUANTISATION, + }, +}; + +const struct visl_ctrls visl_mpeg2_ctrls = { + .ctrls = visl_mpeg2_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_mpeg2_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_vp8_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_VP8_FRAME, + }, +}; + +const struct visl_ctrls visl_vp8_ctrls = { + .ctrls = visl_vp8_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_vp8_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_vp9_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_VP9_FRAME, + }, + { + .cfg.id = V4L2_CID_STATELESS_VP9_COMPRESSED_HDR, + }, +}; + +const struct visl_ctrls visl_vp9_ctrls = { + .ctrls = visl_vp9_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_vp9_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_h264_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_PPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_DECODE_MODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_START_CODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SLICE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_PRED_WEIGHTS, + }, +}; + +const struct visl_ctrls visl_h264_ctrls = { + .ctrls = visl_h264_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_h264_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_hevc_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_PPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SLICE_PARAMS, + /* The absolute maximum for level > 6 */ + .cfg.dims = { 600 }, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_MODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_START_CODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS, + .cfg.dims = { 256 }, + .cfg.max = 0xffffffff, + .cfg.step = 1, + }, + +}; + +const struct visl_ctrls visl_hevc_ctrls = { + .ctrls = visl_hevc_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_hevc_ctrl_descs), +}; + +struct v4l2_ctrl *visl_find_control(struct visl_ctx *ctx, u32 id) +{ + struct v4l2_ctrl_handler *hdl = &ctx->hdl; + + return v4l2_ctrl_find(hdl, id); +} + +void *visl_find_control_data(struct visl_ctx *ctx, u32 id) +{ + struct v4l2_ctrl *ctrl; + + ctrl = visl_find_control(ctx, id); + if (ctrl) + return ctrl->p_cur.p; + + return NULL; +} + +u32 visl_control_num_elems(struct visl_ctx *ctx, u32 id) +{ + struct v4l2_ctrl *ctrl; + + ctrl = visl_find_control(ctx, id); + if (ctrl) + return ctrl->elems; + + return 0; +} + +static void visl_device_release(struct video_device *vdev) +{ + struct visl_dev *dev = container_of(vdev, struct visl_dev, vfd); + + v4l2_device_unregister(&dev->v4l2_dev); + v4l2_m2m_release(dev->m2m_dev); + media_device_cleanup(&dev->mdev); + visl_debugfs_deinit(dev); + kfree(dev); +} + +#define VISL_CONTROLS_COUNT ARRAY_SIZE(visl_controls) + +static int visl_init_ctrls(struct visl_ctx *ctx) +{ + struct visl_dev *dev = ctx->dev; + struct v4l2_ctrl_handler *hdl = &ctx->hdl; + unsigned int ctrl_cnt = 0; + unsigned int i; + unsigned int j; + const struct visl_ctrls *ctrls; + + for (i = 0; i < num_coded_fmts; i++) + ctrl_cnt += visl_coded_fmts[i].ctrls->num_ctrls; + + v4l2_ctrl_handler_init(hdl, ctrl_cnt); + + for (i = 0; i < num_coded_fmts; i++) { + ctrls = visl_coded_fmts[i].ctrls; + for (j = 0; j < ctrls->num_ctrls; j++) + v4l2_ctrl_new_custom(hdl, &ctrls->ctrls[j].cfg, NULL); + } + + if (hdl->error) { + v4l2_err(&dev->v4l2_dev, + "Failed to initialize control handler\n"); + v4l2_ctrl_handler_free(hdl); + return hdl->error; + } + + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +static int visl_open(struct file *file) +{ + struct visl_dev *dev = video_drvdata(file); + struct visl_ctx *ctx = NULL; + int rc = 0; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto unlock; + } + + ctx->tpg_str_buf = kzalloc(TPG_STR_BUF_SZ, GFP_KERNEL); + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + + rc = visl_init_ctrls(ctx); + if (rc) + goto free_ctx; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &visl_queue_init); + + mutex_init(&ctx->vb_mutex); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); + goto free_hdl; + } + + rc = visl_set_default_format(ctx); + if (rc) + goto free_m2m_ctx; + + v4l2_fh_add(&ctx->fh); + + dprintk(dev, "Created instance: %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); + + mutex_unlock(&dev->dev_mutex); + return rc; + +free_m2m_ctx: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +free_hdl: + v4l2_ctrl_handler_free(&ctx->hdl); + v4l2_fh_exit(&ctx->fh); +free_ctx: + kfree(ctx->tpg_str_buf); + kfree(ctx); +unlock: + mutex_unlock(&dev->dev_mutex); + return rc; +} + +static int visl_release(struct file *file) +{ + struct visl_dev *dev = video_drvdata(file); + struct visl_ctx *ctx = visl_file_to_ctx(file); + + dprintk(dev, "Releasing instance %p\n", ctx); + + tpg_free(&ctx->tpg); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&dev->dev_mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&dev->dev_mutex); + + kfree(ctx->tpg_str_buf); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations visl_fops = { + .owner = THIS_MODULE, + .open = visl_open, + .release = visl_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device visl_videodev = { + .name = VISL_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &visl_fops, + .ioctl_ops = &visl_ioctl_ops, + .minor = -1, + .release = visl_device_release, + .device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops visl_m2m_ops = { + .device_run = visl_device_run, +}; + +static const struct media_device_ops visl_m2m_media_ops = { + .req_validate = visl_request_validate, + .req_queue = v4l2_m2m_request_queue, +}; + +static int visl_probe(struct platform_device *pdev) +{ + struct visl_dev *dev; + struct video_device *vfd; + int ret; + int rc; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto error_visl_dev; + + mutex_init(&dev->dev_mutex); + + dev->vfd = visl_videodev; + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + video_set_drvdata(vfd, dev); + + platform_set_drvdata(pdev, dev); + + dev->m2m_dev = v4l2_m2m_init(&visl_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + dev->m2m_dev = NULL; + goto error_dev; + } + + dev->mdev.dev = &pdev->dev; + strscpy(dev->mdev.model, "visl", sizeof(dev->mdev.model)); + strscpy(dev->mdev.bus_info, "platform:visl", + sizeof(dev->mdev.bus_info)); + media_device_init(&dev->mdev); + dev->mdev.ops = &visl_m2m_media_ops; + dev->v4l2_dev.mdev = &dev->mdev; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto error_m2m; + } + + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); + goto error_v4l2; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register mem2mem media device\n"); + goto error_m2m_mc; + } + + rc = visl_debugfs_init(dev); + if (rc) + dprintk(dev, "visl_debugfs_init failed: %d\n" + "Continuing without debugfs support\n", rc); + + return 0; + +error_m2m_mc: + v4l2_m2m_unregister_media_controller(dev->m2m_dev); +error_v4l2: + video_unregister_device(&dev->vfd); + /* visl_device_release called by video_unregister_device to release various objects */ + return ret; +error_m2m: + v4l2_m2m_release(dev->m2m_dev); +error_dev: + v4l2_device_unregister(&dev->v4l2_dev); +error_visl_dev: + kfree(dev); + + return ret; +} + +static int visl_remove(struct platform_device *pdev) +{ + struct visl_dev *dev = platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " VISL_NAME); + +#ifdef CONFIG_MEDIA_CONTROLLER + if (media_devnode_is_registered(dev->mdev.devnode)) { + media_device_unregister(&dev->mdev); + v4l2_m2m_unregister_media_controller(dev->m2m_dev); + } +#endif + video_unregister_device(&dev->vfd); + + return 0; +} + +static struct platform_driver visl_pdrv = { + .probe = visl_probe, + .remove = visl_remove, + .driver = { + .name = VISL_NAME, + }, +}; + +static void visl_dev_release(struct device *dev) {} + +static struct platform_device visl_pdev = { + .name = VISL_NAME, + .dev.release = visl_dev_release, +}; + +static void __exit visl_exit(void) +{ + platform_driver_unregister(&visl_pdrv); + platform_device_unregister(&visl_pdev); +} + +static int __init visl_init(void) +{ + int ret; + + ret = platform_device_register(&visl_pdev); + if (ret) + return ret; + + ret = platform_driver_register(&visl_pdrv); + if (ret) + platform_device_unregister(&visl_pdev); + + return ret; +} + +MODULE_DESCRIPTION("Virtual stateless decoder device"); +MODULE_AUTHOR("Daniel Almeida <daniel.almeida@collabora.com>"); +MODULE_LICENSE("GPL"); + +module_init(visl_init); +module_exit(visl_exit); diff --git a/drivers/media/test-drivers/visl/visl-debugfs.c b/drivers/media/test-drivers/visl/visl-debugfs.c new file mode 100644 index 000000000000..45f2a8268014 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-debugfs.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Debugfs tracing for bitstream buffers. This is similar to VA-API's + * LIBVA_TRACE_BUFDATA in that the raw bitstream can be dumped as a debugging + * aid. + * + * Produces one file per OUTPUT buffer. Files are automatically cleared on + * STREAMOFF unless the module parameter "keep_bitstream_buffers" is set. + */ + +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <media/v4l2-mem2mem.h> + +#include "visl-debugfs.h" + +int visl_debugfs_init(struct visl_dev *dev) +{ + dev->debugfs_root = debugfs_create_dir("visl", NULL); + INIT_LIST_HEAD(&dev->bitstream_blobs); + mutex_init(&dev->bitstream_lock); + + if (IS_ERR(dev->debugfs_root)) + return PTR_ERR(dev->debugfs_root); + + return visl_debugfs_bitstream_init(dev); +} + +int visl_debugfs_bitstream_init(struct visl_dev *dev) +{ + dev->bitstream_debugfs = debugfs_create_dir("bitstream", + dev->debugfs_root); + if (IS_ERR(dev->bitstream_debugfs)) + return PTR_ERR(dev->bitstream_debugfs); + + return 0; +} + +void visl_trace_bitstream(struct visl_ctx *ctx, struct visl_run *run) +{ + u8 *vaddr = vb2_plane_vaddr(&run->src->vb2_buf, 0); + struct visl_blob *blob; + size_t data_sz = vb2_get_plane_payload(&run->src->vb2_buf, 0); + struct dentry *dentry; + char name[32]; + + blob = kzalloc(sizeof(*blob), GFP_KERNEL); + if (!blob) + return; + + blob->blob.data = vzalloc(data_sz); + if (!blob->blob.data) + goto err_vmalloc; + + blob->blob.size = data_sz; + snprintf(name, 32, "bitstream%d", run->src->sequence); + + memcpy(blob->blob.data, vaddr, data_sz); + + dentry = debugfs_create_blob(name, 0444, ctx->dev->bitstream_debugfs, + &blob->blob); + if (IS_ERR(dentry)) + goto err_debugfs; + + blob->dentry = dentry; + + mutex_lock(&ctx->dev->bitstream_lock); + list_add_tail(&blob->list, &ctx->dev->bitstream_blobs); + mutex_unlock(&ctx->dev->bitstream_lock); + + return; + +err_debugfs: + vfree(blob->blob.data); +err_vmalloc: + kfree(blob); +} + +void visl_debugfs_clear_bitstream(struct visl_dev *dev) +{ + struct visl_blob *blob; + struct visl_blob *tmp; + + mutex_lock(&dev->bitstream_lock); + if (list_empty(&dev->bitstream_blobs)) + goto unlock; + + list_for_each_entry_safe(blob, tmp, &dev->bitstream_blobs, list) { + list_del(&blob->list); + debugfs_remove(blob->dentry); + vfree(blob->blob.data); + kfree(blob); + } + +unlock: + mutex_unlock(&dev->bitstream_lock); +} + +void visl_debugfs_bitstream_deinit(struct visl_dev *dev) +{ + visl_debugfs_clear_bitstream(dev); + debugfs_remove_recursive(dev->bitstream_debugfs); + dev->bitstream_debugfs = NULL; +} + +void visl_debugfs_deinit(struct visl_dev *dev) +{ + visl_debugfs_bitstream_deinit(dev); + debugfs_remove_recursive(dev->debugfs_root); + dev->debugfs_root = NULL; +} diff --git a/drivers/media/test-drivers/visl/visl-debugfs.h b/drivers/media/test-drivers/visl/visl-debugfs.h new file mode 100644 index 000000000000..81508f611918 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-debugfs.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Debugfs tracing for bitstream buffers. This is similar to VA-API's + * LIBVA_TRACE_BUFDATA in that the raw bitstream can be dumped as a debugging + * aid. + * + * Produces one file per OUTPUT buffer. Files are automatically cleared on + * STREAMOFF unless the module parameter "keep_bitstream_buffers" is set. + */ + +#include "visl.h" +#include "visl-dec.h" + +#ifdef CONFIG_VISL_DEBUGFS + +int visl_debugfs_init(struct visl_dev *dev); +int visl_debugfs_bitstream_init(struct visl_dev *dev); +void visl_trace_bitstream(struct visl_ctx *ctx, struct visl_run *run); +void visl_debugfs_clear_bitstream(struct visl_dev *dev); +void visl_debugfs_bitstream_deinit(struct visl_dev *dev); +void visl_debugfs_deinit(struct visl_dev *dev); + +#else + +static inline int visl_debugfs_init(struct visl_dev *dev) +{ + return 0; +} + +static inline int visl_debugfs_bitstream_init(struct visl_dev *dev) +{ + return 0; +} + +static inline void visl_trace_bitstream(struct visl_ctx *ctx, struct visl_run *run) {} +static inline void visl_debugfs_clear_bitstream(struct visl_dev *dev) {} +static inline void visl_debugfs_bitstream_deinit(struct visl_dev *dev) {} +static inline void visl_debugfs_deinit(struct visl_dev *dev) {} + +#endif diff --git a/drivers/media/test-drivers/visl/visl-dec.c b/drivers/media/test-drivers/visl/visl-dec.c new file mode 100644 index 000000000000..318d675e5668 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-dec.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Contains the virtual decoder logic. The functions here control the + * tracing/TPG on a per-frame basis + */ + +#include "visl.h" +#include "visl-debugfs.h" +#include "visl-dec.h" +#include "visl-trace-fwht.h" +#include "visl-trace-mpeg2.h" +#include "visl-trace-vp8.h" +#include "visl-trace-vp9.h" +#include "visl-trace-h264.h" +#include "visl-trace-hevc.h" + +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <media/v4l2-mem2mem.h> +#include <media/tpg/v4l2-tpg.h> + +static void *plane_vaddr(struct tpg_data *tpg, struct vb2_buffer *buf, + u32 p, u32 bpl[TPG_MAX_PLANES], u32 h) +{ + u32 i; + void *vbuf; + + if (p == 0 || tpg_g_buffers(tpg) > 1) + return vb2_plane_vaddr(buf, p); + vbuf = vb2_plane_vaddr(buf, 0); + for (i = 0; i < p; i++) + vbuf += bpl[i] * h / tpg->vdownsampling[i]; + return vbuf; +} + +static void visl_get_ref_frames(struct visl_ctx *ctx, u8 *buf, + __kernel_size_t buflen, struct visl_run *run) +{ + struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q; + char header[] = "Reference frames:\n"; + u32 i; + u32 len; + + len = scnprintf(buf, buflen, header); + buf += len; + buflen -= len; + + switch (ctx->current_codec) { + case VISL_CODEC_NONE: + break; + + case VISL_CODEC_FWHT: { + struct vb2_buffer *vb2_buf; + + vb2_buf = vb2_find_buffer(cap_q, run->fwht.params->backward_ref_ts); + + scnprintf(buf, buflen, "backwards_ref_ts: %lld, vb2_idx: %d", + run->fwht.params->backward_ref_ts, + vb2_buf ? vb2_buf->index : -1); + break; + } + + case VISL_CODEC_MPEG2: { + struct vb2_buffer *b_ref; + struct vb2_buffer *f_ref; + + b_ref = vb2_find_buffer(cap_q, run->mpeg2.pic->backward_ref_ts); + f_ref = vb2_find_buffer(cap_q, run->mpeg2.pic->forward_ref_ts); + + scnprintf(buf, buflen, + "backward_ref_ts: %llu, vb2_idx: %d\n" + "forward_ref_ts: %llu, vb2_idx: %d\n", + run->mpeg2.pic->backward_ref_ts, + b_ref ? b_ref->index : -1, + run->mpeg2.pic->forward_ref_ts, + f_ref ? f_ref->index : -1); + break; + } + + case VISL_CODEC_VP8: { + struct vb2_buffer *last; + struct vb2_buffer *golden; + struct vb2_buffer *alt; + + last = vb2_find_buffer(cap_q, run->vp8.frame->last_frame_ts); + golden = vb2_find_buffer(cap_q, run->vp8.frame->golden_frame_ts); + alt = vb2_find_buffer(cap_q, run->vp8.frame->alt_frame_ts); + + scnprintf(buf, buflen, + "last_ref_ts: %llu, vb2_idx: %d\n" + "golden_ref_ts: %llu, vb2_idx: %d\n" + "alt_ref_ts: %llu, vb2_idx: %d\n", + run->vp8.frame->last_frame_ts, + last ? last->index : -1, + run->vp8.frame->golden_frame_ts, + golden ? golden->index : -1, + run->vp8.frame->alt_frame_ts, + alt ? alt->index : -1); + break; + } + + case VISL_CODEC_VP9: { + struct vb2_buffer *last; + struct vb2_buffer *golden; + struct vb2_buffer *alt; + + last = vb2_find_buffer(cap_q, run->vp9.frame->last_frame_ts); + golden = vb2_find_buffer(cap_q, run->vp9.frame->golden_frame_ts); + alt = vb2_find_buffer(cap_q, run->vp9.frame->alt_frame_ts); + + scnprintf(buf, buflen, + "last_ref_ts: %llu, vb2_idx: %d\n" + "golden_ref_ts: %llu, vb2_idx: %d\n" + "alt_ref_ts: %llu, vb2_idx: %d\n", + run->vp9.frame->last_frame_ts, + last ? last->index : -1, + run->vp9.frame->golden_frame_ts, + golden ? golden->index : -1, + run->vp9.frame->alt_frame_ts, + alt ? alt->index : -1); + break; + } + + case VISL_CODEC_H264: { + char entry[] = "dpb[%d]:%u, vb2_index: %d\n"; + struct vb2_buffer *vb2_buf; + + for (i = 0; i < ARRAY_SIZE(run->h264.dpram->dpb); i++) { + vb2_buf = vb2_find_buffer(cap_q, run->h264.dpram->dpb[i].reference_ts); + len = scnprintf(buf, buflen, entry, i, + run->h264.dpram->dpb[i].reference_ts, + vb2_buf ? vb2_buf->index : -1); + buf += len; + buflen -= len; + } + + break; + } + + case VISL_CODEC_HEVC: { + char entry[] = "dpb[%d]:%u, vb2_index: %d\n"; + struct vb2_buffer *vb2_buf; + + for (i = 0; i < ARRAY_SIZE(run->hevc.dpram->dpb); i++) { + vb2_buf = vb2_find_buffer(cap_q, run->hevc.dpram->dpb[i].timestamp); + len = scnprintf(buf, buflen, entry, i, + run->hevc.dpram->dpb[i].timestamp, + vb2_buf ? vb2_buf->index : -1); + buf += len; + buflen -= len; + } + + break; + } + } +} + +static char *visl_get_vb2_state(enum vb2_buffer_state state) +{ + switch (state) { + case VB2_BUF_STATE_DEQUEUED: + return "Dequeued"; + case VB2_BUF_STATE_IN_REQUEST: + return "In request"; + case VB2_BUF_STATE_PREPARING: + return "Preparing"; + case VB2_BUF_STATE_QUEUED: + return "Queued"; + case VB2_BUF_STATE_ACTIVE: + return "Active"; + case VB2_BUF_STATE_DONE: + return "Done"; + case VB2_BUF_STATE_ERROR: + return "Error"; + default: + return ""; + } +} + +static int visl_fill_bytesused(struct vb2_v4l2_buffer *v4l2_vb2_buf, char *buf, size_t bufsz) +{ + int len = 0; + u32 i; + + for (i = 0; i < v4l2_vb2_buf->vb2_buf.num_planes; i++) + len += scnprintf(buf, bufsz, + "bytesused[%u]: %u length[%u]: %u data_offset[%u]: %u", + i, v4l2_vb2_buf->planes[i].bytesused, + i, v4l2_vb2_buf->planes[i].length, + i, v4l2_vb2_buf->planes[i].data_offset); + + return len; +} + +static void visl_tpg_fill_sequence(struct visl_ctx *ctx, + struct visl_run *run, char buf[], size_t bufsz) +{ + u32 stream_ms; + + stream_ms = jiffies_to_msecs(get_jiffies_64() - ctx->capture_streamon_jiffies); + + scnprintf(buf, bufsz, + "stream time: %02d:%02d:%02d:%03d sequence:%u timestamp:%lld field:%s", + (stream_ms / (60 * 60 * 1000)) % 24, + (stream_ms / (60 * 1000)) % 60, + (stream_ms / 1000) % 60, + stream_ms % 1000, + run->dst->sequence, + run->dst->vb2_buf.timestamp, + (run->dst->field == V4L2_FIELD_ALTERNATE) ? + (run->dst->field == V4L2_FIELD_TOP ? + " top" : " bottom") : "none"); +} + +static void visl_tpg_fill(struct visl_ctx *ctx, struct visl_run *run) +{ + u8 *basep[TPG_MAX_PLANES][2]; + char *buf = ctx->tpg_str_buf; + char *tmp = buf; + char *line_str; + u32 line = 1; + const u32 line_height = 16; + u32 len; + struct vb2_queue *out_q = &ctx->fh.m2m_ctx->out_q_ctx.q; + struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q; + struct v4l2_pix_format_mplane *coded_fmt = &ctx->coded_fmt.fmt.pix_mp; + struct v4l2_pix_format_mplane *decoded_fmt = &ctx->decoded_fmt.fmt.pix_mp; + u32 p; + u32 i; + + for (p = 0; p < tpg_g_planes(&ctx->tpg); p++) { + void *vbuf = plane_vaddr(&ctx->tpg, + &run->dst->vb2_buf, p, + ctx->tpg.bytesperline, + ctx->tpg.buf_height); + + tpg_calc_text_basep(&ctx->tpg, basep, p, vbuf); + tpg_fill_plane_buffer(&ctx->tpg, 0, p, vbuf); + } + + visl_tpg_fill_sequence(ctx, run, buf, TPG_STR_BUF_SZ); + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + frame_dprintk(ctx->dev, run->dst->sequence, ""); + line++; + + visl_get_ref_frames(ctx, buf, TPG_STR_BUF_SZ, run); + + while ((line_str = strsep(&tmp, "\n")) && strlen(line_str)) { + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, line_str); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", line_str); + } + + frame_dprintk(ctx->dev, run->dst->sequence, ""); + line++; + + scnprintf(buf, + TPG_STR_BUF_SZ, + "OUTPUT pixelformat: %c%c%c%c, resolution: %dx%d, num_planes: %d", + coded_fmt->pixelformat, + (coded_fmt->pixelformat >> 8) & 0xff, + (coded_fmt->pixelformat >> 16) & 0xff, + (coded_fmt->pixelformat >> 24) & 0xff, + coded_fmt->width, + coded_fmt->height, + coded_fmt->num_planes); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + for (i = 0; i < coded_fmt->num_planes; i++) { + scnprintf(buf, + TPG_STR_BUF_SZ, + "plane[%d]: bytesperline: %d, sizeimage: %d", + i, + coded_fmt->plane_fmt[i].bytesperline, + coded_fmt->plane_fmt[i].sizeimage); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + } + + line++; + frame_dprintk(ctx->dev, run->dst->sequence, ""); + scnprintf(buf, TPG_STR_BUF_SZ, "Output queue status:"); + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + len = 0; + for (i = 0; i < out_q->num_buffers; i++) { + char entry[] = "index: %u, state: %s, request_fd: %d, "; + u32 old_len = len; + char *q_status = visl_get_vb2_state(out_q->bufs[i]->state); + + len += scnprintf(&buf[len], TPG_STR_BUF_SZ - len, + entry, i, q_status, + to_vb2_v4l2_buffer(out_q->bufs[i])->request_fd); + + len += visl_fill_bytesused(to_vb2_v4l2_buffer(out_q->bufs[i]), + &buf[len], + TPG_STR_BUF_SZ - len); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, &buf[old_len]); + frame_dprintk(ctx->dev, run->dst->sequence, "%s", &buf[old_len]); + } + + line++; + frame_dprintk(ctx->dev, run->dst->sequence, ""); + + scnprintf(buf, + TPG_STR_BUF_SZ, + "CAPTURE pixelformat: %c%c%c%c, resolution: %dx%d, num_planes: %d", + decoded_fmt->pixelformat, + (decoded_fmt->pixelformat >> 8) & 0xff, + (decoded_fmt->pixelformat >> 16) & 0xff, + (decoded_fmt->pixelformat >> 24) & 0xff, + decoded_fmt->width, + decoded_fmt->height, + decoded_fmt->num_planes); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + for (i = 0; i < decoded_fmt->num_planes; i++) { + scnprintf(buf, + TPG_STR_BUF_SZ, + "plane[%d]: bytesperline: %d, sizeimage: %d", + i, + decoded_fmt->plane_fmt[i].bytesperline, + decoded_fmt->plane_fmt[i].sizeimage); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + } + + line++; + frame_dprintk(ctx->dev, run->dst->sequence, ""); + scnprintf(buf, TPG_STR_BUF_SZ, "Capture queue status:"); + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + len = 0; + for (i = 0; i < cap_q->num_buffers; i++) { + u32 old_len = len; + char *q_status = visl_get_vb2_state(cap_q->bufs[i]->state); + + len += scnprintf(&buf[len], TPG_STR_BUF_SZ - len, + "index: %u, status: %s, timestamp: %llu, is_held: %d", + cap_q->bufs[i]->index, q_status, + cap_q->bufs[i]->timestamp, + to_vb2_v4l2_buffer(cap_q->bufs[i])->is_held); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, &buf[old_len]); + frame_dprintk(ctx->dev, run->dst->sequence, "%s", &buf[old_len]); + } +} + +static void visl_trace_ctrls(struct visl_ctx *ctx, struct visl_run *run) +{ + int i; + + switch (ctx->current_codec) { + default: + case VISL_CODEC_NONE: + break; + case VISL_CODEC_FWHT: + trace_v4l2_ctrl_fwht_params(run->fwht.params); + break; + case VISL_CODEC_MPEG2: + trace_v4l2_ctrl_mpeg2_sequence(run->mpeg2.seq); + trace_v4l2_ctrl_mpeg2_picture(run->mpeg2.pic); + trace_v4l2_ctrl_mpeg2_quantisation(run->mpeg2.quant); + break; + case VISL_CODEC_VP8: + trace_v4l2_ctrl_vp8_frame(run->vp8.frame); + trace_v4l2_ctrl_vp8_entropy(run->vp8.frame); + break; + case VISL_CODEC_VP9: + trace_v4l2_ctrl_vp9_frame(run->vp9.frame); + trace_v4l2_ctrl_vp9_compressed_hdr(run->vp9.probs); + trace_v4l2_ctrl_vp9_compressed_coeff(run->vp9.probs); + trace_v4l2_vp9_mv_probs(&run->vp9.probs->mv); + break; + case VISL_CODEC_H264: + trace_v4l2_ctrl_h264_sps(run->h264.sps); + trace_v4l2_ctrl_h264_pps(run->h264.pps); + trace_v4l2_ctrl_h264_scaling_matrix(run->h264.sm); + trace_v4l2_ctrl_h264_slice_params(run->h264.spram); + + for (i = 0; i < ARRAY_SIZE(run->h264.spram->ref_pic_list0); i++) + trace_v4l2_h264_ref_pic_list0(&run->h264.spram->ref_pic_list0[i], i); + for (i = 0; i < ARRAY_SIZE(run->h264.spram->ref_pic_list0); i++) + trace_v4l2_h264_ref_pic_list1(&run->h264.spram->ref_pic_list1[i], i); + + trace_v4l2_ctrl_h264_decode_params(run->h264.dpram); + + for (i = 0; i < ARRAY_SIZE(run->h264.dpram->dpb); i++) + trace_v4l2_h264_dpb_entry(&run->h264.dpram->dpb[i], i); + + trace_v4l2_ctrl_h264_pred_weights(run->h264.pwht); + break; + case VISL_CODEC_HEVC: + trace_v4l2_ctrl_hevc_sps(run->hevc.sps); + trace_v4l2_ctrl_hevc_pps(run->hevc.pps); + trace_v4l2_ctrl_hevc_slice_params(run->hevc.spram); + trace_v4l2_ctrl_hevc_scaling_matrix(run->hevc.sm); + trace_v4l2_ctrl_hevc_decode_params(run->hevc.dpram); + + for (i = 0; i < ARRAY_SIZE(run->hevc.dpram->dpb); i++) + trace_v4l2_hevc_dpb_entry(&run->hevc.dpram->dpb[i]); + + trace_v4l2_hevc_pred_weight_table(&run->hevc.spram->pred_weight_table); + break; + } +} + +void visl_device_run(void *priv) +{ + struct visl_ctx *ctx = priv; + struct visl_run run = {}; + struct media_request *src_req; + + run.src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + run.dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + /* Apply request(s) controls if needed. */ + src_req = run.src->vb2_buf.req_obj.req; + + if (src_req) + v4l2_ctrl_request_setup(src_req, &ctx->hdl); + + v4l2_m2m_buf_copy_metadata(run.src, run.dst, true); + run.dst->sequence = ctx->q_data[V4L2_M2M_DST].sequence++; + run.src->sequence = ctx->q_data[V4L2_M2M_SRC].sequence++; + run.dst->field = ctx->decoded_fmt.fmt.pix.field; + + switch (ctx->current_codec) { + default: + case VISL_CODEC_NONE: + break; + case VISL_CODEC_FWHT: + run.fwht.params = visl_find_control_data(ctx, V4L2_CID_STATELESS_FWHT_PARAMS); + break; + case VISL_CODEC_MPEG2: + run.mpeg2.seq = visl_find_control_data(ctx, V4L2_CID_STATELESS_MPEG2_SEQUENCE); + run.mpeg2.pic = visl_find_control_data(ctx, V4L2_CID_STATELESS_MPEG2_PICTURE); + run.mpeg2.quant = visl_find_control_data(ctx, + V4L2_CID_STATELESS_MPEG2_QUANTISATION); + break; + case VISL_CODEC_VP8: + run.vp8.frame = visl_find_control_data(ctx, V4L2_CID_STATELESS_VP8_FRAME); + break; + case VISL_CODEC_VP9: + run.vp9.frame = visl_find_control_data(ctx, V4L2_CID_STATELESS_VP9_FRAME); + run.vp9.probs = visl_find_control_data(ctx, V4L2_CID_STATELESS_VP9_COMPRESSED_HDR); + break; + case VISL_CODEC_H264: + run.h264.sps = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_SPS); + run.h264.pps = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_PPS); + run.h264.sm = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_SCALING_MATRIX); + run.h264.spram = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_SLICE_PARAMS); + run.h264.dpram = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); + run.h264.pwht = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_PRED_WEIGHTS); + break; + case VISL_CODEC_HEVC: + run.hevc.sps = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS); + run.hevc.pps = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_PPS); + run.hevc.spram = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SLICE_PARAMS); + run.hevc.sm = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); + run.hevc.dpram = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); + break; + } + + frame_dprintk(ctx->dev, run.dst->sequence, + "Got OUTPUT buffer sequence %d, timestamp %llu\n", + run.src->sequence, run.src->vb2_buf.timestamp); + + frame_dprintk(ctx->dev, run.dst->sequence, + "Got CAPTURE buffer sequence %d, timestamp %llu\n", + run.dst->sequence, run.dst->vb2_buf.timestamp); + + visl_tpg_fill(ctx, &run); + visl_trace_ctrls(ctx, &run); + + if (bitstream_trace_frame_start > -1 && + run.dst->sequence >= bitstream_trace_frame_start && + run.dst->sequence < bitstream_trace_frame_start + bitstream_trace_nframes) + visl_trace_bitstream(ctx, &run); + + /* Complete request(s) controls if needed. */ + if (src_req) + v4l2_ctrl_request_complete(src_req, &ctx->hdl); + + if (visl_transtime_ms) + usleep_range(visl_transtime_ms * 1000, 2 * visl_transtime_ms * 1000); + + v4l2_m2m_buf_done_and_job_finish(ctx->dev->m2m_dev, + ctx->fh.m2m_ctx, VB2_BUF_STATE_DONE); +} diff --git a/drivers/media/test-drivers/visl/visl-dec.h b/drivers/media/test-drivers/visl/visl-dec.h new file mode 100644 index 000000000000..4a706a9de02e --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-dec.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Contains the virtual decoder logic. The functions here control the + * tracing/TPG on a per-frame basis + */ + +#ifndef _VISL_DEC_H_ +#define _VISL_DEC_H_ + +#include "visl.h" + +struct visl_fwht_run { + const struct v4l2_ctrl_fwht_params *params; +}; + +struct visl_mpeg2_run { + const struct v4l2_ctrl_mpeg2_sequence *seq; + const struct v4l2_ctrl_mpeg2_picture *pic; + const struct v4l2_ctrl_mpeg2_quantisation *quant; +}; + +struct visl_vp8_run { + const struct v4l2_ctrl_vp8_frame *frame; +}; + +struct visl_vp9_run { + const struct v4l2_ctrl_vp9_frame *frame; + const struct v4l2_ctrl_vp9_compressed_hdr *probs; +}; + +struct visl_h264_run { + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; + const struct v4l2_ctrl_h264_scaling_matrix *sm; + const struct v4l2_ctrl_h264_slice_params *spram; + const struct v4l2_ctrl_h264_decode_params *dpram; + const struct v4l2_ctrl_h264_pred_weights *pwht; +}; + +struct visl_hevc_run { + const struct v4l2_ctrl_hevc_sps *sps; + const struct v4l2_ctrl_hevc_pps *pps; + const struct v4l2_ctrl_hevc_slice_params *spram; + const struct v4l2_ctrl_hevc_scaling_matrix *sm; + const struct v4l2_ctrl_hevc_decode_params *dpram; +}; + +struct visl_run { + struct vb2_v4l2_buffer *src; + struct vb2_v4l2_buffer *dst; + + union { + struct visl_fwht_run fwht; + struct visl_mpeg2_run mpeg2; + struct visl_vp8_run vp8; + struct visl_vp9_run vp9; + struct visl_h264_run h264; + struct visl_hevc_run hevc; + }; +}; + +int visl_dec_start(struct visl_ctx *ctx); +int visl_dec_stop(struct visl_ctx *ctx); +int visl_job_ready(void *priv); +void visl_device_run(void *priv); + +#endif /* _VISL_DEC_H_ */ diff --git a/drivers/media/test-drivers/visl/visl-trace-fwht.h b/drivers/media/test-drivers/visl/visl-trace-fwht.h new file mode 100644 index 000000000000..54b119359ff5 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-fwht.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_FWHT_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_FWHT_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_fwht_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_fwht_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_fwht_params *p), + TP_ARGS(p), + TP_STRUCT__entry( + __field(u64, backward_ref_ts) + __field(u32, version) + __field(u32, width) + __field(u32, height) + __field(u32, flags) + __field(u32, colorspace) + __field(u32, xfer_func) + __field(u32, ycbcr_enc) + __field(u32, quantization) + ), + TP_fast_assign( + __entry->backward_ref_ts = p->backward_ref_ts; + __entry->version = p->version; + __entry->width = p->width; + __entry->height = p->height; + __entry->flags = p->flags; + __entry->colorspace = p->colorspace; + __entry->xfer_func = p->xfer_func; + __entry->ycbcr_enc = p->ycbcr_enc; + __entry->quantization = p->quantization; + ), + TP_printk("backward_ref_ts %llu version %u width %u height %u flags %s colorspace %u xfer_func %u ycbcr_enc %u quantization %u", + __entry->backward_ref_ts, __entry->version, __entry->width, __entry->height, + __print_flags(__entry->flags, "|", + {V4L2_FWHT_FL_IS_INTERLACED, "IS_INTERLACED"}, + {V4L2_FWHT_FL_IS_BOTTOM_FIRST, "IS_BOTTOM_FIRST"}, + {V4L2_FWHT_FL_IS_ALTERNATE, "IS_ALTERNATE"}, + {V4L2_FWHT_FL_IS_BOTTOM_FIELD, "IS_BOTTOM_FIELD"}, + {V4L2_FWHT_FL_LUMA_IS_UNCOMPRESSED, "LUMA_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_CB_IS_UNCOMPRESSED, "CB_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_CR_IS_UNCOMPRESSED, "CR_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_ALPHA_IS_UNCOMPRESSED, "ALPHA_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_I_FRAME, "I_FRAME"}, + {V4L2_FWHT_FL_PIXENC_HSV, "PIXENC_HSV"}, + {V4L2_FWHT_FL_PIXENC_RGB, "PIXENC_RGB"}, + {V4L2_FWHT_FL_PIXENC_YUV, "PIXENC_YUV"}), + __entry->colorspace, __entry->xfer_func, __entry->ycbcr_enc, + __entry->quantization) +); + +DEFINE_EVENT(v4l2_ctrl_fwht_params_tmpl, v4l2_ctrl_fwht_params, + TP_PROTO(const struct v4l2_ctrl_fwht_params *p), + TP_ARGS(p) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-fwht +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-h264.h b/drivers/media/test-drivers/visl/visl-trace-h264.h new file mode 100644 index 000000000000..d84296a01deb --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-h264.h @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_H264_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_H264_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_h264_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_sps_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_sps *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_sps, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nprofile_idc %u\n" + "constraint_set_flags %s\n" + "level_idc %u\n" + "seq_parameter_set_id %u\n" + "chroma_format_idc %u\n" + "bit_depth_luma_minus8 %u\n" + "bit_depth_chroma_minus8 %u\n" + "log2_max_frame_num_minus4 %u\n" + "pic_order_cnt_type %u\n" + "log2_max_pic_order_cnt_lsb_minus4 %u\n" + "max_num_ref_frames %u\n" + "num_ref_frames_in_pic_order_cnt_cycle %u\n" + "offset_for_ref_frame %s\n" + "offset_for_non_ref_pic %d\n" + "offset_for_top_to_bottom_field %d\n" + "pic_width_in_mbs_minus1 %u\n" + "pic_height_in_map_units_minus1 %u\n" + "flags %s", + __entry->s.profile_idc, + __print_flags(__entry->s.constraint_set_flags, "|", + {V4L2_H264_SPS_CONSTRAINT_SET0_FLAG, "CONSTRAINT_SET0_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET1_FLAG, "CONSTRAINT_SET1_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET2_FLAG, "CONSTRAINT_SET2_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET3_FLAG, "CONSTRAINT_SET3_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET4_FLAG, "CONSTRAINT_SET4_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET5_FLAG, "CONSTRAINT_SET5_FLAG"}), + __entry->s.level_idc, + __entry->s.seq_parameter_set_id, + __entry->s.chroma_format_idc, + __entry->s.bit_depth_luma_minus8, + __entry->s.bit_depth_chroma_minus8, + __entry->s.log2_max_frame_num_minus4, + __entry->s.pic_order_cnt_type, + __entry->s.log2_max_pic_order_cnt_lsb_minus4, + __entry->s.max_num_ref_frames, + __entry->s.num_ref_frames_in_pic_order_cnt_cycle, + __print_array(__entry->s.offset_for_ref_frame, + ARRAY_SIZE(__entry->s.offset_for_ref_frame), + sizeof(__entry->s.offset_for_ref_frame[0])), + __entry->s.offset_for_non_ref_pic, + __entry->s.offset_for_top_to_bottom_field, + __entry->s.pic_width_in_mbs_minus1, + __entry->s.pic_height_in_map_units_minus1, + __print_flags(__entry->s.flags, "|", + {V4L2_H264_SPS_FLAG_SEPARATE_COLOUR_PLANE, "SEPARATE_COLOUR_PLANE"}, + {V4L2_H264_SPS_FLAG_QPPRIME_Y_ZERO_TRANSFORM_BYPASS, "QPPRIME_Y_ZERO_TRANSFORM_BYPASS"}, + {V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO, "DELTA_PIC_ORDER_ALWAYS_ZERO"}, + {V4L2_H264_SPS_FLAG_GAPS_IN_FRAME_NUM_VALUE_ALLOWED, "GAPS_IN_FRAME_NUM_VALUE_ALLOWED"}, + {V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY, "FRAME_MBS_ONLY"}, + {V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD, "MB_ADAPTIVE_FRAME_FIELD"}, + {V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE, "DIRECT_8X8_INFERENCE"} + )) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_pps_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_pps *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_pps, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\npic_parameter_set_id %u\n" + "seq_parameter_set_id %u\n" + "num_slice_groups_minus1 %u\n" + "num_ref_idx_l0_default_active_minus1 %u\n" + "num_ref_idx_l1_default_active_minus1 %u\n" + "weighted_bipred_idc %u\n" + "pic_init_qp_minus26 %d\n" + "pic_init_qs_minus26 %d\n" + "chroma_qp_index_offset %d\n" + "second_chroma_qp_index_offset %d\n" + "flags %s", + __entry->p.pic_parameter_set_id, + __entry->p.seq_parameter_set_id, + __entry->p.num_slice_groups_minus1, + __entry->p.num_ref_idx_l0_default_active_minus1, + __entry->p.num_ref_idx_l1_default_active_minus1, + __entry->p.weighted_bipred_idc, + __entry->p.pic_init_qp_minus26, + __entry->p.pic_init_qs_minus26, + __entry->p.chroma_qp_index_offset, + __entry->p.second_chroma_qp_index_offset, + __print_flags(__entry->p.flags, "|", + {V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE, "ENTROPY_CODING_MODE"}, + {V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT, "BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT"}, + {V4L2_H264_PPS_FLAG_WEIGHTED_PRED, "WEIGHTED_PRED"}, + {V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT, "DEBLOCKING_FILTER_CONTROL_PRESENT"}, + {V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED, "CONSTRAINED_INTRA_PRED"}, + {V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT, "REDUNDANT_PIC_CNT_PRESENT"}, + {V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE, "TRANSFORM_8X8_MODE"}, + {V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT, "SCALING_MATRIX_PRESENT"} + )) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_scaling_matrix_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_scaling_matrix *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_scaling_matrix, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nscaling_list_4x4 {%s}\nscaling_list_8x8 {%s}", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_4x4, + sizeof(__entry->s.scaling_list_4x4), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_8x8, + sizeof(__entry->s.scaling_list_8x8), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_pred_weights_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_pred_weights *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_pred_weights, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\nluma_log2_weight_denom %u\n" + "chroma_log2_weight_denom %u\n" + "weight_factor[0].luma_weight %s\n" + "weight_factor[0].luma_offset %s\n" + "weight_factor[0].chroma_weight {%s}\n" + "weight_factor[0].chroma_offset {%s}\n" + "weight_factor[1].luma_weight %s\n" + "weight_factor[1].luma_offset %s\n" + "weight_factor[1].chroma_weight {%s}\n" + "weight_factor[1].chroma_offset {%s}\n", + __entry->p.luma_log2_weight_denom, + __entry->p.chroma_log2_weight_denom, + __print_array(__entry->p.weight_factors[0].luma_weight, + ARRAY_SIZE(__entry->p.weight_factors[0].luma_weight), + sizeof(__entry->p.weight_factors[0].luma_weight[0])), + __print_array(__entry->p.weight_factors[0].luma_offset, + ARRAY_SIZE(__entry->p.weight_factors[0].luma_offset), + sizeof(__entry->p.weight_factors[0].luma_offset[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[0].chroma_weight, + sizeof(__entry->p.weight_factors[0].chroma_weight), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[0].chroma_offset, + sizeof(__entry->p.weight_factors[0].chroma_offset), + false), + __print_array(__entry->p.weight_factors[1].luma_weight, + ARRAY_SIZE(__entry->p.weight_factors[1].luma_weight), + sizeof(__entry->p.weight_factors[1].luma_weight[0])), + __print_array(__entry->p.weight_factors[1].luma_offset, + ARRAY_SIZE(__entry->p.weight_factors[1].luma_offset), + sizeof(__entry->p.weight_factors[1].luma_offset[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[1].chroma_weight, + sizeof(__entry->p.weight_factors[1].chroma_weight), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[1].chroma_offset, + sizeof(__entry->p.weight_factors[1].chroma_offset), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_slice_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_slice_params *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_slice_params, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nheader_bit_size %u\n" + "first_mb_in_slice %u\n" + "slice_type %s\n" + "colour_plane_id %u\n" + "redundant_pic_cnt %u\n" + "cabac_init_idc %u\n" + "slice_qp_delta %d\n" + "slice_qs_delta %d\n" + "disable_deblocking_filter_idc %u\n" + "slice_alpha_c0_offset_div2 %u\n" + "slice_beta_offset_div2 %u\n" + "num_ref_idx_l0_active_minus1 %u\n" + "num_ref_idx_l1_active_minus1 %u\n" + "flags %s", + __entry->s.header_bit_size, + __entry->s.first_mb_in_slice, + __print_symbolic(__entry->s.slice_type, + {V4L2_H264_SLICE_TYPE_P, "P"}, + {V4L2_H264_SLICE_TYPE_B, "B"}, + {V4L2_H264_SLICE_TYPE_I, "I"}, + {V4L2_H264_SLICE_TYPE_SP, "SP"}, + {V4L2_H264_SLICE_TYPE_SI, "SI"}), + __entry->s.colour_plane_id, + __entry->s.redundant_pic_cnt, + __entry->s.cabac_init_idc, + __entry->s.slice_qp_delta, + __entry->s.slice_qs_delta, + __entry->s.disable_deblocking_filter_idc, + __entry->s.slice_alpha_c0_offset_div2, + __entry->s.slice_beta_offset_div2, + __entry->s.num_ref_idx_l0_active_minus1, + __entry->s.num_ref_idx_l1_active_minus1, + __print_flags(__entry->s.flags, "|", + {V4L2_H264_SLICE_FLAG_DIRECT_SPATIAL_MV_PRED, "DIRECT_SPATIAL_MV_PRED"}, + {V4L2_H264_SLICE_FLAG_SP_FOR_SWITCH, "SP_FOR_SWITCH"}) + ) +); + +DECLARE_EVENT_CLASS(v4l2_h264_reference_tmpl, + TP_PROTO(const struct v4l2_h264_reference *r, int i), + TP_ARGS(r, i), + TP_STRUCT__entry(__field_struct(struct v4l2_h264_reference, r) + __field(int, i)), + TP_fast_assign(__entry->r = *r; __entry->i = i;), + TP_printk("[%d]: fields %s index %u", + __entry->i, + __print_flags(__entry->r.fields, "|", + {V4L2_H264_TOP_FIELD_REF, "TOP_FIELD_REF"}, + {V4L2_H264_BOTTOM_FIELD_REF, "BOTTOM_FIELD_REF"}, + {V4L2_H264_FRAME_REF, "FRAME_REF"}), + __entry->r.index + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_decode_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_decode_params *d), + TP_ARGS(d), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_decode_params, d)), + TP_fast_assign(__entry->d = *d), + TP_printk("\nnal_ref_idc %u\n" + "frame_num %u\n" + "top_field_order_cnt %d\n" + "bottom_field_order_cnt %d\n" + "idr_pic_id %u\n" + "pic_order_cnt_lsb %u\n" + "delta_pic_order_cnt_bottom %d\n" + "delta_pic_order_cnt0 %d\n" + "delta_pic_order_cnt1 %d\n" + "dec_ref_pic_marking_bit_size %u\n" + "pic_order_cnt_bit_size %u\n" + "slice_group_change_cycle %u\n" + "flags %s\n", + __entry->d.nal_ref_idc, + __entry->d.frame_num, + __entry->d.top_field_order_cnt, + __entry->d.bottom_field_order_cnt, + __entry->d.idr_pic_id, + __entry->d.pic_order_cnt_lsb, + __entry->d.delta_pic_order_cnt_bottom, + __entry->d.delta_pic_order_cnt0, + __entry->d.delta_pic_order_cnt1, + __entry->d.dec_ref_pic_marking_bit_size, + __entry->d.pic_order_cnt_bit_size, + __entry->d.slice_group_change_cycle, + __print_flags(__entry->d.flags, "|", + {V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC, "IDR_PIC"}, + {V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC, "FIELD_PIC"}, + {V4L2_H264_DECODE_PARAM_FLAG_BOTTOM_FIELD, "BOTTOM_FIELD"}, + {V4L2_H264_DECODE_PARAM_FLAG_PFRAME, "PFRAME"}, + {V4L2_H264_DECODE_PARAM_FLAG_BFRAME, "BFRAME"}) + ) +); + +DECLARE_EVENT_CLASS(v4l2_h264_dpb_entry_tmpl, + TP_PROTO(const struct v4l2_h264_dpb_entry *e, int i), + TP_ARGS(e, i), + TP_STRUCT__entry(__field_struct(struct v4l2_h264_dpb_entry, e) + __field(int, i)), + TP_fast_assign(__entry->e = *e; __entry->i = i;), + TP_printk("[%d]: reference_ts %llu, pic_num %u frame_num %u fields %s " + "top_field_order_cnt %d bottom_field_order_cnt %d flags %s", + __entry->i, + __entry->e.reference_ts, + __entry->e.pic_num, + __entry->e.frame_num, + __print_flags(__entry->e.fields, "|", + {V4L2_H264_TOP_FIELD_REF, "TOP_FIELD_REF"}, + {V4L2_H264_BOTTOM_FIELD_REF, "BOTTOM_FIELD_REF"}, + {V4L2_H264_FRAME_REF, "FRAME_REF"}), + __entry->e.top_field_order_cnt, + __entry->e.bottom_field_order_cnt, + __print_flags(__entry->e.flags, "|", + {V4L2_H264_DPB_ENTRY_FLAG_VALID, "VALID"}, + {V4L2_H264_DPB_ENTRY_FLAG_ACTIVE, "ACTIVE"}, + {V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM, "LONG_TERM"}, + {V4L2_H264_DPB_ENTRY_FLAG_FIELD, "FIELD"}) + + ) +); + +DEFINE_EVENT(v4l2_ctrl_h264_sps_tmpl, v4l2_ctrl_h264_sps, + TP_PROTO(const struct v4l2_ctrl_h264_sps *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_h264_pps_tmpl, v4l2_ctrl_h264_pps, + TP_PROTO(const struct v4l2_ctrl_h264_pps *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_h264_scaling_matrix_tmpl, v4l2_ctrl_h264_scaling_matrix, + TP_PROTO(const struct v4l2_ctrl_h264_scaling_matrix *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_h264_pred_weights_tmpl, v4l2_ctrl_h264_pred_weights, + TP_PROTO(const struct v4l2_ctrl_h264_pred_weights *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_h264_slice_params_tmpl, v4l2_ctrl_h264_slice_params, + TP_PROTO(const struct v4l2_ctrl_h264_slice_params *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_h264_reference_tmpl, v4l2_h264_ref_pic_list0, + TP_PROTO(const struct v4l2_h264_reference *r, int i), + TP_ARGS(r, i) +); + +DEFINE_EVENT(v4l2_h264_reference_tmpl, v4l2_h264_ref_pic_list1, + TP_PROTO(const struct v4l2_h264_reference *r, int i), + TP_ARGS(r, i) +); + +DEFINE_EVENT(v4l2_ctrl_h264_decode_params_tmpl, v4l2_ctrl_h264_decode_params, + TP_PROTO(const struct v4l2_ctrl_h264_decode_params *d), + TP_ARGS(d) +); + +DEFINE_EVENT(v4l2_h264_dpb_entry_tmpl, v4l2_h264_dpb_entry, + TP_PROTO(const struct v4l2_h264_dpb_entry *e, int i), + TP_ARGS(e, i) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-h264 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-hevc.h b/drivers/media/test-drivers/visl/visl-trace-hevc.h new file mode 100644 index 000000000000..837b8ec12e97 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-hevc.h @@ -0,0 +1,405 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#if !defined(_VISL_TRACE_HEVC_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_HEVC_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_hevc_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_sps_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_sps *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_sps, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nvideo_parameter_set_id %u\n" + "seq_parameter_set_id %u\n" + "pic_width_in_luma_samples %u\n" + "pic_height_in_luma_samples %u\n" + "bit_depth_luma_minus8 %u\n" + "bit_depth_chroma_minus8 %u\n" + "log2_max_pic_order_cnt_lsb_minus4 %u\n" + "sps_max_dec_pic_buffering_minus1 %u\n" + "sps_max_num_reorder_pics %u\n" + "sps_max_latency_increase_plus1 %u\n" + "log2_min_luma_coding_block_size_minus3 %u\n" + "log2_diff_max_min_luma_coding_block_size %u\n" + "log2_min_luma_transform_block_size_minus2 %u\n" + "log2_diff_max_min_luma_transform_block_size %u\n" + "max_transform_hierarchy_depth_inter %u\n" + "max_transform_hierarchy_depth_intra %u\n" + "pcm_sample_bit_depth_luma_minus1 %u\n" + "pcm_sample_bit_depth_chroma_minus1 %u\n" + "log2_min_pcm_luma_coding_block_size_minus3 %u\n" + "log2_diff_max_min_pcm_luma_coding_block_size %u\n" + "num_short_term_ref_pic_sets %u\n" + "num_long_term_ref_pics_sps %u\n" + "chroma_format_idc %u\n" + "sps_max_sub_layers_minus1 %u\n" + "flags %s", + __entry->s.video_parameter_set_id, + __entry->s.seq_parameter_set_id, + __entry->s.pic_width_in_luma_samples, + __entry->s.pic_height_in_luma_samples, + __entry->s.bit_depth_luma_minus8, + __entry->s.bit_depth_chroma_minus8, + __entry->s.log2_max_pic_order_cnt_lsb_minus4, + __entry->s.sps_max_dec_pic_buffering_minus1, + __entry->s.sps_max_num_reorder_pics, + __entry->s.sps_max_latency_increase_plus1, + __entry->s.log2_min_luma_coding_block_size_minus3, + __entry->s.log2_diff_max_min_luma_coding_block_size, + __entry->s.log2_min_luma_transform_block_size_minus2, + __entry->s.log2_diff_max_min_luma_transform_block_size, + __entry->s.max_transform_hierarchy_depth_inter, + __entry->s.max_transform_hierarchy_depth_intra, + __entry->s.pcm_sample_bit_depth_luma_minus1, + __entry->s.pcm_sample_bit_depth_chroma_minus1, + __entry->s.log2_min_pcm_luma_coding_block_size_minus3, + __entry->s.log2_diff_max_min_pcm_luma_coding_block_size, + __entry->s.num_short_term_ref_pic_sets, + __entry->s.num_long_term_ref_pics_sps, + __entry->s.chroma_format_idc, + __entry->s.sps_max_sub_layers_minus1, + __print_flags(__entry->s.flags, "|", + {V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE, "SEPARATE_COLOUR_PLANE"}, + {V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED, "SCALING_LIST_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_AMP_ENABLED, "AMP_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET, "SAMPLE_ADAPTIVE_OFFSET"}, + {V4L2_HEVC_SPS_FLAG_PCM_ENABLED, "PCM_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED, "V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED"}, + {V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT, "LONG_TERM_REF_PICS_PRESENT"}, + {V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED, "TEMPORAL_MVP_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED, "STRONG_INTRA_SMOOTHING_ENABLED"} + )) + +); + + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_pps_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_pps *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_pps, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\npic_parameter_set_id %u\n" + "num_extra_slice_header_bits %u\n" + "num_ref_idx_l0_default_active_minus1 %u\n" + "num_ref_idx_l1_default_active_minus1 %u\n" + "init_qp_minus26 %d\n" + "diff_cu_qp_delta_depth %u\n" + "pps_cb_qp_offset %d\n" + "pps_cr_qp_offset %d\n" + "num_tile_columns_minus1 %d\n" + "num_tile_rows_minus1 %d\n" + "column_width_minus1 %s\n" + "row_height_minus1 %s\n" + "pps_beta_offset_div2 %d\n" + "pps_tc_offset_div2 %d\n" + "log2_parallel_merge_level_minus2 %u\n" + "flags %s", + __entry->p.pic_parameter_set_id, + __entry->p.num_extra_slice_header_bits, + __entry->p.num_ref_idx_l0_default_active_minus1, + __entry->p.num_ref_idx_l1_default_active_minus1, + __entry->p.init_qp_minus26, + __entry->p.diff_cu_qp_delta_depth, + __entry->p.pps_cb_qp_offset, + __entry->p.pps_cr_qp_offset, + __entry->p.num_tile_columns_minus1, + __entry->p.num_tile_rows_minus1, + __print_array(__entry->p.column_width_minus1, + ARRAY_SIZE(__entry->p.column_width_minus1), + sizeof(__entry->p.column_width_minus1[0])), + __print_array(__entry->p.row_height_minus1, + ARRAY_SIZE(__entry->p.row_height_minus1), + sizeof(__entry->p.row_height_minus1[0])), + __entry->p.pps_beta_offset_div2, + __entry->p.pps_tc_offset_div2, + __entry->p.log2_parallel_merge_level_minus2, + __print_flags(__entry->p.flags, "|", + {V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED, "DEPENDENT_SLICE_SEGMENT_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT, "OUTPUT_FLAG_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED, "SIGN_DATA_HIDING_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT, "CABAC_INIT_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED, "CONSTRAINED_INTRA_PRED"}, + {V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED, "CU_QP_DELTA_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT, "PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED, "WEIGHTED_PRED"}, + {V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED, "WEIGHTED_BIPRED"}, + {V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED, "TRANSQUANT_BYPASS_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_TILES_ENABLED, "TILES_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED, "ENTROPY_CODING_SYNC_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED, "LOOP_FILTER_ACROSS_TILES_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED, "PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED, "DEBLOCKING_FILTER_OVERRIDE_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER, "DISABLE_DEBLOCKING_FILTER"}, + {V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT, "LISTS_MODIFICATION_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT, "SLICE_SEGMENT_HEADER_EXTENSION_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT, "DEBLOCKING_FILTER_CONTROL_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING, "UNIFORM_SPACING"} + )) + +); + + + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_slice_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_slice_params *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_slice_params, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nbit_size %u\n" + "data_byte_offset %u\n" + "num_entry_point_offsets %u\n" + "nal_unit_type %u\n" + "nuh_temporal_id_plus1 %u\n" + "slice_type %u\n" + "colour_plane_id %u\n" + "slice_pic_order_cnt %d\n" + "num_ref_idx_l0_active_minus1 %u\n" + "num_ref_idx_l1_active_minus1 %u\n" + "collocated_ref_idx %u\n" + "five_minus_max_num_merge_cand %u\n" + "slice_qp_delta %d\n" + "slice_cb_qp_offset %d\n" + "slice_cr_qp_offset %d\n" + "slice_act_y_qp_offset %d\n" + "slice_act_cb_qp_offset %d\n" + "slice_act_cr_qp_offset %d\n" + "slice_beta_offset_div2 %d\n" + "slice_tc_offset_div2 %d\n" + "pic_struct %u\n" + "slice_segment_addr %u\n" + "ref_idx_l0 %s\n" + "ref_idx_l1 %s\n" + "short_term_ref_pic_set_size %u\n" + "long_term_ref_pic_set_size %u\n" + "flags %s", + __entry->s.bit_size, + __entry->s.data_byte_offset, + __entry->s.num_entry_point_offsets, + __entry->s.nal_unit_type, + __entry->s.nuh_temporal_id_plus1, + __entry->s.slice_type, + __entry->s.colour_plane_id, + __entry->s.slice_pic_order_cnt, + __entry->s.num_ref_idx_l0_active_minus1, + __entry->s.num_ref_idx_l1_active_minus1, + __entry->s.collocated_ref_idx, + __entry->s.five_minus_max_num_merge_cand, + __entry->s.slice_qp_delta, + __entry->s.slice_cb_qp_offset, + __entry->s.slice_cr_qp_offset, + __entry->s.slice_act_y_qp_offset, + __entry->s.slice_act_cb_qp_offset, + __entry->s.slice_act_cr_qp_offset, + __entry->s.slice_beta_offset_div2, + __entry->s.slice_tc_offset_div2, + __entry->s.pic_struct, + __entry->s.slice_segment_addr, + __print_array(__entry->s.ref_idx_l0, + ARRAY_SIZE(__entry->s.ref_idx_l0), + sizeof(__entry->s.ref_idx_l0[0])), + __print_array(__entry->s.ref_idx_l1, + ARRAY_SIZE(__entry->s.ref_idx_l1), + sizeof(__entry->s.ref_idx_l1[0])), + __entry->s.short_term_ref_pic_set_size, + __entry->s.long_term_ref_pic_set_size, + __print_flags(__entry->s.flags, "|", + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA, "SLICE_SAO_LUMA"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA, "SLICE_SAO_CHROMA"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED, "SLICE_TEMPORAL_MVP_ENABLED"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO, "MVD_L1_ZERO"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT, "CABAC_INIT"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0, "COLLOCATED_FROM_L0"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_USE_INTEGER_MV, "USE_INTEGER_MV"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED, "SLICE_DEBLOCKING_FILTER_DISABLED"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED, "SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT, "DEPENDENT_SLICE_SEGMENT"} + + )) +); + +DECLARE_EVENT_CLASS(v4l2_hevc_pred_weight_table_tmpl, + TP_PROTO(const struct v4l2_hevc_pred_weight_table *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_hevc_pred_weight_table, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\ndelta_luma_weight_l0 %s\n" + "luma_offset_l0 %s\n" + "delta_chroma_weight_l0 {%s}\n" + "chroma_offset_l0 {%s}\n" + "delta_luma_weight_l1 %s\n" + "luma_offset_l1 %s\n" + "delta_chroma_weight_l1 {%s}\n" + "chroma_offset_l1 {%s}\n" + "luma_log2_weight_denom %d\n" + "delta_chroma_log2_weight_denom %d\n", + __print_array(__entry->p.delta_luma_weight_l0, + ARRAY_SIZE(__entry->p.delta_luma_weight_l0), + sizeof(__entry->p.delta_luma_weight_l0[0])), + __print_array(__entry->p.luma_offset_l0, + ARRAY_SIZE(__entry->p.luma_offset_l0), + sizeof(__entry->p.luma_offset_l0[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.delta_chroma_weight_l0, + sizeof(__entry->p.delta_chroma_weight_l0), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.chroma_offset_l0, + sizeof(__entry->p.chroma_offset_l0), + false), + __print_array(__entry->p.delta_luma_weight_l1, + ARRAY_SIZE(__entry->p.delta_luma_weight_l1), + sizeof(__entry->p.delta_luma_weight_l1[0])), + __print_array(__entry->p.luma_offset_l1, + ARRAY_SIZE(__entry->p.luma_offset_l1), + sizeof(__entry->p.luma_offset_l1[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.delta_chroma_weight_l1, + sizeof(__entry->p.delta_chroma_weight_l1), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.chroma_offset_l1, + sizeof(__entry->p.chroma_offset_l1), + false), + __entry->p.luma_log2_weight_denom, + __entry->p.delta_chroma_log2_weight_denom + + )) + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_scaling_matrix_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_scaling_matrix *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_scaling_matrix, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nscaling_list_4x4 {%s}\n" + "scaling_list_8x8 {%s}\n" + "scaling_list_16x16 {%s}\n" + "scaling_list_32x32 {%s}\n" + "scaling_list_dc_coef_16x16 %s\n" + "scaling_list_dc_coef_32x32 %s\n", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_4x4, + sizeof(__entry->s.scaling_list_4x4), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_8x8, + sizeof(__entry->s.scaling_list_8x8), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_16x16, + sizeof(__entry->s.scaling_list_16x16), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_32x32, + sizeof(__entry->s.scaling_list_32x32), + false), + __print_array(__entry->s.scaling_list_dc_coef_16x16, + ARRAY_SIZE(__entry->s.scaling_list_dc_coef_16x16), + sizeof(__entry->s.scaling_list_dc_coef_16x16[0])), + __print_array(__entry->s.scaling_list_dc_coef_32x32, + ARRAY_SIZE(__entry->s.scaling_list_dc_coef_32x32), + sizeof(__entry->s.scaling_list_dc_coef_32x32[0])) + )) + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_decode_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_decode_params *d), + TP_ARGS(d), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_decode_params, d)), + TP_fast_assign(__entry->d = *d), + TP_printk("\npic_order_cnt_val %d\n" + "short_term_ref_pic_set_size %u\n" + "long_term_ref_pic_set_size %u\n" + "num_active_dpb_entries %u\n" + "num_poc_st_curr_before %u\n" + "num_poc_st_curr_after %u\n" + "num_poc_lt_curr %u\n" + "poc_st_curr_before %s\n" + "poc_st_curr_after %s\n" + "poc_lt_curr %s\n" + "flags %s", + __entry->d.pic_order_cnt_val, + __entry->d.short_term_ref_pic_set_size, + __entry->d.long_term_ref_pic_set_size, + __entry->d.num_active_dpb_entries, + __entry->d.num_poc_st_curr_before, + __entry->d.num_poc_st_curr_after, + __entry->d.num_poc_lt_curr, + __print_array(__entry->d.poc_st_curr_before, + ARRAY_SIZE(__entry->d.poc_st_curr_before), + sizeof(__entry->d.poc_st_curr_before[0])), + __print_array(__entry->d.poc_st_curr_after, + ARRAY_SIZE(__entry->d.poc_st_curr_after), + sizeof(__entry->d.poc_st_curr_after[0])), + __print_array(__entry->d.poc_lt_curr, + ARRAY_SIZE(__entry->d.poc_lt_curr), + sizeof(__entry->d.poc_lt_curr[0])), + __print_flags(__entry->d.flags, "|", + {V4L2_HEVC_DECODE_PARAM_FLAG_IRAP_PIC, "IRAP_PIC"}, + {V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC, "IDR_PIC"}, + {V4L2_HEVC_DECODE_PARAM_FLAG_NO_OUTPUT_OF_PRIOR, "NO_OUTPUT_OF_PRIOR"} + )) +); + + +DECLARE_EVENT_CLASS(v4l2_hevc_dpb_entry_tmpl, + TP_PROTO(const struct v4l2_hevc_dpb_entry *e), + TP_ARGS(e), + TP_STRUCT__entry(__field_struct(struct v4l2_hevc_dpb_entry, e)), + TP_fast_assign(__entry->e = *e), + TP_printk("\ntimestamp %llu\n" + "flags %s\n" + "field_pic %u\n" + "pic_order_cnt_val %d\n", + __entry->e.timestamp, + __print_flags(__entry->e.flags, "|", + {V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE, "LONG_TERM_REFERENCE"} + ), + __entry->e.field_pic, + __entry->e.pic_order_cnt_val + )) + +DEFINE_EVENT(v4l2_ctrl_hevc_sps_tmpl, v4l2_ctrl_hevc_sps, + TP_PROTO(const struct v4l2_ctrl_hevc_sps *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_pps_tmpl, v4l2_ctrl_hevc_pps, + TP_PROTO(const struct v4l2_ctrl_hevc_pps *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_slice_params_tmpl, v4l2_ctrl_hevc_slice_params, + TP_PROTO(const struct v4l2_ctrl_hevc_slice_params *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_hevc_pred_weight_table_tmpl, v4l2_hevc_pred_weight_table, + TP_PROTO(const struct v4l2_hevc_pred_weight_table *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_scaling_matrix_tmpl, v4l2_ctrl_hevc_scaling_matrix, + TP_PROTO(const struct v4l2_ctrl_hevc_scaling_matrix *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_decode_params_tmpl, v4l2_ctrl_hevc_decode_params, + TP_PROTO(const struct v4l2_ctrl_hevc_decode_params *d), + TP_ARGS(d) +); + +DEFINE_EVENT(v4l2_hevc_dpb_entry_tmpl, v4l2_hevc_dpb_entry, + TP_PROTO(const struct v4l2_hevc_dpb_entry *e), + TP_ARGS(e) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-hevc +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-mpeg2.h b/drivers/media/test-drivers/visl/visl-trace-mpeg2.h new file mode 100644 index 000000000000..ba6c65481194 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-mpeg2.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_MPEG2_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_MPEG2_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_mpeg2_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_mpeg2_seq_tmpl, + TP_PROTO(const struct v4l2_ctrl_mpeg2_sequence *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_mpeg2_sequence, s)), + TP_fast_assign(__entry->s = *s;), + TP_printk("\nhorizontal_size %u\nvertical_size %u\nvbv_buffer_size %u\n" + "profile_and_level_indication %u\nchroma_format %u\nflags %s\n", + __entry->s.horizontal_size, + __entry->s.vertical_size, + __entry->s.vbv_buffer_size, + __entry->s.profile_and_level_indication, + __entry->s.chroma_format, + __print_flags(__entry->s.flags, "|", + {V4L2_MPEG2_SEQ_FLAG_PROGRESSIVE, "PROGRESSIVE"}) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_mpeg2_pic_tmpl, + TP_PROTO(const struct v4l2_ctrl_mpeg2_picture *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_mpeg2_picture, p)), + TP_fast_assign(__entry->p = *p;), + TP_printk("\nbackward_ref_ts %llu\nforward_ref_ts %llu\nflags %s\nf_code {%s}\n" + "picture_coding_type: %u\npicture_structure %u\nintra_dc_precision %u\n", + __entry->p.backward_ref_ts, + __entry->p.forward_ref_ts, + __print_flags(__entry->p.flags, "|", + {V4L2_MPEG2_PIC_FLAG_TOP_FIELD_FIRST, "TOP_FIELD_FIRST"}, + {V4L2_MPEG2_PIC_FLAG_FRAME_PRED_DCT, "FRAME_PRED_DCT"}, + {V4L2_MPEG2_PIC_FLAG_CONCEALMENT_MV, "CONCEALMENT_MV"}, + {V4L2_MPEG2_PIC_FLAG_Q_SCALE_TYPE, "Q_SCALE_TYPE"}, + {V4L2_MPEG2_PIC_FLAG_INTRA_VLC, "INTA_VLC"}, + {V4L2_MPEG2_PIC_FLAG_ALT_SCAN, "ALT_SCAN"}, + {V4L2_MPEG2_PIC_FLAG_REPEAT_FIRST, "REPEAT_FIRST"}, + {V4L2_MPEG2_PIC_FLAG_PROGRESSIVE, "PROGRESSIVE"}), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.f_code, + sizeof(__entry->p.f_code), + false), + __entry->p.picture_coding_type, + __entry->p.picture_structure, + __entry->p.intra_dc_precision + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_mpeg2_quant_tmpl, + TP_PROTO(const struct v4l2_ctrl_mpeg2_quantisation *q), + TP_ARGS(q), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_mpeg2_quantisation, q)), + TP_fast_assign(__entry->q = *q;), + TP_printk("\nintra_quantiser_matrix %s\nnon_intra_quantiser_matrix %s\n" + "chroma_intra_quantiser_matrix %s\nchroma_non_intra_quantiser_matrix %s\n", + __print_array(__entry->q.intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.intra_quantiser_matrix), + sizeof(__entry->q.intra_quantiser_matrix[0])), + __print_array(__entry->q.non_intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.non_intra_quantiser_matrix), + sizeof(__entry->q.non_intra_quantiser_matrix[0])), + __print_array(__entry->q.chroma_intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.chroma_intra_quantiser_matrix), + sizeof(__entry->q.chroma_intra_quantiser_matrix[0])), + __print_array(__entry->q.chroma_non_intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.chroma_non_intra_quantiser_matrix), + sizeof(__entry->q.chroma_non_intra_quantiser_matrix[0])) + ) +) + +DEFINE_EVENT(v4l2_ctrl_mpeg2_seq_tmpl, v4l2_ctrl_mpeg2_sequence, + TP_PROTO(const struct v4l2_ctrl_mpeg2_sequence *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_mpeg2_pic_tmpl, v4l2_ctrl_mpeg2_picture, + TP_PROTO(const struct v4l2_ctrl_mpeg2_picture *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_mpeg2_quant_tmpl, v4l2_ctrl_mpeg2_quantisation, + TP_PROTO(const struct v4l2_ctrl_mpeg2_quantisation *q), + TP_ARGS(q) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-mpeg2 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-points.c b/drivers/media/test-drivers/visl/visl-trace-points.c new file mode 100644 index 000000000000..f7b866534f1e --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-points.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "visl.h" + +#define CREATE_TRACE_POINTS +#include "visl-trace-fwht.h" +#include "visl-trace-mpeg2.h" +#include "visl-trace-vp8.h" +#include "visl-trace-vp9.h" +#include "visl-trace-h264.h" +#include "visl-trace-hevc.h" diff --git a/drivers/media/test-drivers/visl/visl-trace-vp8.h b/drivers/media/test-drivers/visl/visl-trace-vp8.h new file mode 100644 index 000000000000..dabe17d69ddc --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-vp8.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_VP8_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_VP8_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_vp8_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp8_entropy_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp8_frame, f)), + TP_fast_assign(__entry->f = *f;), + TP_printk("\nentropy.coeff_probs {%s}\n" + "entropy.y_mode_probs %s\n" + "entropy.uv_mode_probs %s\n" + "entropy.mv_probs {%s}", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->f.entropy.coeff_probs, + sizeof(__entry->f.entropy.coeff_probs), + false), + __print_array(__entry->f.entropy.y_mode_probs, + ARRAY_SIZE(__entry->f.entropy.y_mode_probs), + sizeof(__entry->f.entropy.y_mode_probs[0])), + __print_array(__entry->f.entropy.uv_mode_probs, + ARRAY_SIZE(__entry->f.entropy.uv_mode_probs), + sizeof(__entry->f.entropy.uv_mode_probs[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->f.entropy.mv_probs, + sizeof(__entry->f.entropy.mv_probs), + false) + ) +) + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp8_frame_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp8_frame, f)), + TP_fast_assign(__entry->f = *f;), + TP_printk("\nsegment.quant_update %s\n" + "segment.lf_update %s\n" + "segment.segment_probs %s\n" + "segment.flags %s\n" + "lf.ref_frm_delta %s\n" + "lf.mb_mode_delta %s\n" + "lf.sharpness_level %u\n" + "lf.level %u\n" + "lf.flags %s\n" + "quant.y_ac_qi %u\n" + "quant.y_dc_delta %d\n" + "quant.y2_dc_delta %d\n" + "quant.y2_ac_delta %d\n" + "quant.uv_dc_delta %d\n" + "quant.uv_ac_delta %d\n" + "coder_state.range %u\n" + "coder_state.value %u\n" + "coder_state.bit_count %u\n" + "width %u\n" + "height %u\n" + "horizontal_scale %u\n" + "vertical_scale %u\n" + "version %u\n" + "prob_skip_false %u\n" + "prob_intra %u\n" + "prob_last %u\n" + "prob_gf %u\n" + "num_dct_parts %u\n" + "first_part_size %u\n" + "first_part_header_bits %u\n" + "dct_part_sizes %s\n" + "last_frame_ts %llu\n" + "golden_frame_ts %llu\n" + "alt_frame_ts %llu\n" + "flags %s", + __print_array(__entry->f.segment.quant_update, + ARRAY_SIZE(__entry->f.segment.quant_update), + sizeof(__entry->f.segment.quant_update[0])), + __print_array(__entry->f.segment.lf_update, + ARRAY_SIZE(__entry->f.segment.lf_update), + sizeof(__entry->f.segment.lf_update[0])), + __print_array(__entry->f.segment.segment_probs, + ARRAY_SIZE(__entry->f.segment.segment_probs), + sizeof(__entry->f.segment.segment_probs[0])), + __print_flags(__entry->f.segment.flags, "|", + {V4L2_VP8_SEGMENT_FLAG_ENABLED, "SEGMENT_ENABLED"}, + {V4L2_VP8_SEGMENT_FLAG_UPDATE_MAP, "SEGMENT_UPDATE_MAP"}, + {V4L2_VP8_SEGMENT_FLAG_UPDATE_FEATURE_DATA, "SEGMENT_UPDATE_FEATURE_DATA"}, + {V4L2_VP8_SEGMENT_FLAG_DELTA_VALUE_MODE, "SEGMENT_DELTA_VALUE_MODE"}), + __print_array(__entry->f.lf.ref_frm_delta, + ARRAY_SIZE(__entry->f.lf.ref_frm_delta), + sizeof(__entry->f.lf.ref_frm_delta[0])), + __print_array(__entry->f.lf.mb_mode_delta, + ARRAY_SIZE(__entry->f.lf.mb_mode_delta), + sizeof(__entry->f.lf.mb_mode_delta[0])), + __entry->f.lf.sharpness_level, + __entry->f.lf.level, + __print_flags(__entry->f.lf.flags, "|", + {V4L2_VP8_LF_ADJ_ENABLE, "LF_ADJ_ENABLED"}, + {V4L2_VP8_LF_DELTA_UPDATE, "LF_DELTA_UPDATE"}, + {V4L2_VP8_LF_FILTER_TYPE_SIMPLE, "LF_FILTER_TYPE_SIMPLE"}), + __entry->f.quant.y_ac_qi, + __entry->f.quant.y_dc_delta, + __entry->f.quant.y2_dc_delta, + __entry->f.quant.y2_ac_delta, + __entry->f.quant.uv_dc_delta, + __entry->f.quant.uv_ac_delta, + __entry->f.coder_state.range, + __entry->f.coder_state.value, + __entry->f.coder_state.bit_count, + __entry->f.width, + __entry->f.height, + __entry->f.horizontal_scale, + __entry->f.vertical_scale, + __entry->f.version, + __entry->f.prob_skip_false, + __entry->f.prob_intra, + __entry->f.prob_last, + __entry->f.prob_gf, + __entry->f.num_dct_parts, + __entry->f.first_part_size, + __entry->f.first_part_header_bits, + __print_array(__entry->f.dct_part_sizes, + ARRAY_SIZE(__entry->f.dct_part_sizes), + sizeof(__entry->f.dct_part_sizes[0])), + __entry->f.last_frame_ts, + __entry->f.golden_frame_ts, + __entry->f.alt_frame_ts, + __print_flags(__entry->f.flags, "|", + {V4L2_VP8_FRAME_FLAG_KEY_FRAME, "KEY_FRAME"}, + {V4L2_VP8_FRAME_FLAG_EXPERIMENTAL, "EXPERIMENTAL"}, + {V4L2_VP8_FRAME_FLAG_SHOW_FRAME, "SHOW_FRAME"}, + {V4L2_VP8_FRAME_FLAG_MB_NO_SKIP_COEFF, "MB_NO_SKIP_COEFF"}, + {V4L2_VP8_FRAME_FLAG_SIGN_BIAS_GOLDEN, "SIGN_BIAS_GOLDEN"}, + {V4L2_VP8_FRAME_FLAG_SIGN_BIAS_ALT, "SIGN_BIAS_ALT"}) + ) +); + +DEFINE_EVENT(v4l2_ctrl_vp8_frame_tmpl, v4l2_ctrl_vp8_frame, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f) +); + +DEFINE_EVENT(v4l2_ctrl_vp8_entropy_tmpl, v4l2_ctrl_vp8_entropy, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-vp8 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-vp9.h b/drivers/media/test-drivers/visl/visl-trace-vp9.h new file mode 100644 index 000000000000..362b92b07f93 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-vp9.h @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_VP9_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_VP9_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_vp9_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp9_frame_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp9_frame *f), + TP_ARGS(f), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp9_frame, f)), + TP_fast_assign(__entry->f = *f;), + TP_printk("\nlf.ref_deltas %s\n" + "lf.mode_deltas %s\n" + "lf.level %u\n" + "lf.sharpness %u\n" + "lf.flags %s\n" + "quant.base_q_idx %u\n" + "quant.delta_q_y_dc %d\n" + "quant.delta_q_uv_dc %d\n" + "quant.delta_q_uv_ac %d\n" + "seg.feature_data {%s}\n" + "seg.feature_enabled %s\n" + "seg.tree_probs %s\n" + "seg.pred_probs %s\n" + "seg.flags %s\n" + "flags %s\n" + "compressed_header_size %u\n" + "uncompressed_header_size %u\n" + "frame_width_minus_1 %u\n" + "frame_height_minus_1 %u\n" + "render_width_minus_1 %u\n" + "render_height_minus_1 %u\n" + "last_frame_ts %llu\n" + "golden_frame_ts %llu\n" + "alt_frame_ts %llu\n" + "ref_frame_sign_bias %s\n" + "reset_frame_context %s\n" + "frame_context_idx %u\n" + "profile %u\n" + "bit_depth %u\n" + "interpolation_filter %s\n" + "tile_cols_log2 %u\n" + "tile_rows_log_2 %u\n" + "reference_mode %s\n", + __print_array(__entry->f.lf.ref_deltas, + ARRAY_SIZE(__entry->f.lf.ref_deltas), + sizeof(__entry->f.lf.ref_deltas[0])), + __print_array(__entry->f.lf.mode_deltas, + ARRAY_SIZE(__entry->f.lf.mode_deltas), + sizeof(__entry->f.lf.mode_deltas[0])), + __entry->f.lf.level, + __entry->f.lf.sharpness, + __print_flags(__entry->f.lf.flags, "|", + {V4L2_VP9_LOOP_FILTER_FLAG_DELTA_ENABLED, "DELTA_ENABLED"}, + {V4L2_VP9_LOOP_FILTER_FLAG_DELTA_UPDATE, "DELTA_UPDATE"}), + __entry->f.quant.base_q_idx, + __entry->f.quant.delta_q_y_dc, + __entry->f.quant.delta_q_uv_dc, + __entry->f.quant.delta_q_uv_ac, + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->f.seg.feature_data, + sizeof(__entry->f.seg.feature_data), + false), + __print_array(__entry->f.seg.feature_enabled, + ARRAY_SIZE(__entry->f.seg.feature_enabled), + sizeof(__entry->f.seg.feature_enabled[0])), + __print_array(__entry->f.seg.tree_probs, + ARRAY_SIZE(__entry->f.seg.tree_probs), + sizeof(__entry->f.seg.tree_probs[0])), + __print_array(__entry->f.seg.pred_probs, + ARRAY_SIZE(__entry->f.seg.pred_probs), + sizeof(__entry->f.seg.pred_probs[0])), + __print_flags(__entry->f.seg.flags, "|", + {V4L2_VP9_SEGMENTATION_FLAG_ENABLED, "ENABLED"}, + {V4L2_VP9_SEGMENTATION_FLAG_UPDATE_MAP, "UPDATE_MAP"}, + {V4L2_VP9_SEGMENTATION_FLAG_TEMPORAL_UPDATE, "TEMPORAL_UPDATE"}, + {V4L2_VP9_SEGMENTATION_FLAG_UPDATE_DATA, "UPDATE_DATA"}, + {V4L2_VP9_SEGMENTATION_FLAG_ABS_OR_DELTA_UPDATE, "ABS_OR_DELTA_UPDATE"}), + __print_flags(__entry->f.flags, "|", + {V4L2_VP9_FRAME_FLAG_KEY_FRAME, "KEY_FRAME"}, + {V4L2_VP9_FRAME_FLAG_SHOW_FRAME, "SHOW_FRAME"}, + {V4L2_VP9_FRAME_FLAG_ERROR_RESILIENT, "ERROR_RESILIENT"}, + {V4L2_VP9_FRAME_FLAG_INTRA_ONLY, "INTRA_ONLY"}, + {V4L2_VP9_FRAME_FLAG_ALLOW_HIGH_PREC_MV, "ALLOW_HIGH_PREC_MV"}, + {V4L2_VP9_FRAME_FLAG_REFRESH_FRAME_CTX, "REFRESH_FRAME_CTX"}, + {V4L2_VP9_FRAME_FLAG_PARALLEL_DEC_MODE, "PARALLEL_DEC_MODE"}, + {V4L2_VP9_FRAME_FLAG_X_SUBSAMPLING, "X_SUBSAMPLING"}, + {V4L2_VP9_FRAME_FLAG_Y_SUBSAMPLING, "Y_SUBSAMPLING"}, + {V4L2_VP9_FRAME_FLAG_COLOR_RANGE_FULL_SWING, "COLOR_RANGE_FULL_SWING"}), + __entry->f.compressed_header_size, + __entry->f.uncompressed_header_size, + __entry->f.frame_width_minus_1, + __entry->f.frame_height_minus_1, + __entry->f.render_width_minus_1, + __entry->f.render_height_minus_1, + __entry->f.last_frame_ts, + __entry->f.golden_frame_ts, + __entry->f.alt_frame_ts, + __print_symbolic(__entry->f.ref_frame_sign_bias, + {V4L2_VP9_SIGN_BIAS_LAST, "SIGN_BIAS_LAST"}, + {V4L2_VP9_SIGN_BIAS_GOLDEN, "SIGN_BIAS_GOLDEN"}, + {V4L2_VP9_SIGN_BIAS_ALT, "SIGN_BIAS_ALT"}), + __print_symbolic(__entry->f.reset_frame_context, + {V4L2_VP9_RESET_FRAME_CTX_NONE, "RESET_FRAME_CTX_NONE"}, + {V4L2_VP9_RESET_FRAME_CTX_SPEC, "RESET_FRAME_CTX_SPEC"}, + {V4L2_VP9_RESET_FRAME_CTX_ALL, "RESET_FRAME_CTX_ALL"}), + __entry->f.frame_context_idx, + __entry->f.profile, + __entry->f.bit_depth, + __print_symbolic(__entry->f.interpolation_filter, + {V4L2_VP9_INTERP_FILTER_EIGHTTAP, "INTERP_FILTER_EIGHTTAP"}, + {V4L2_VP9_INTERP_FILTER_EIGHTTAP_SMOOTH, "INTERP_FILTER_EIGHTTAP_SMOOTH"}, + {V4L2_VP9_INTERP_FILTER_EIGHTTAP_SHARP, "INTERP_FILTER_EIGHTTAP_SHARP"}, + {V4L2_VP9_INTERP_FILTER_BILINEAR, "INTERP_FILTER_BILINEAR"}, + {V4L2_VP9_INTERP_FILTER_SWITCHABLE, "INTERP_FILTER_SWITCHABLE"}), + __entry->f.tile_cols_log2, + __entry->f.tile_rows_log2, + __print_symbolic(__entry->f.reference_mode, + {V4L2_VP9_REFERENCE_MODE_SINGLE_REFERENCE, "REFERENCE_MODE_SINGLE_REFERENCE"}, + {V4L2_VP9_REFERENCE_MODE_COMPOUND_REFERENCE, "REFERENCE_MODE_COMPOUND_REFERENCE"}, + {V4L2_VP9_REFERENCE_MODE_SELECT, "REFERENCE_MODE_SELECT"})) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp9_compressed_hdr_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp9_compressed_hdr, h)), + TP_fast_assign(__entry->h = *h;), + TP_printk("\ntx_mode %s\n" + "tx8 {%s}\n" + "tx16 {%s}\n" + "tx32 {%s}\n" + "skip %s\n" + "inter_mode {%s}\n" + "interp_filter {%s}\n" + "is_inter %s\n" + "comp_mode %s\n" + "single_ref {%s}\n" + "comp_ref %s\n" + "y_mode {%s}\n" + "uv_mode {%s}\n" + "partition {%s}\n", + __print_symbolic(__entry->h.tx_mode, + {V4L2_VP9_TX_MODE_ONLY_4X4, "TX_MODE_ONLY_4X4"}, + {V4L2_VP9_TX_MODE_ALLOW_8X8, "TX_MODE_ALLOW_8X8"}, + {V4L2_VP9_TX_MODE_ALLOW_16X16, "TX_MODE_ALLOW_16X16"}, + {V4L2_VP9_TX_MODE_ALLOW_32X32, "TX_MODE_ALLOW_32X32"}, + {V4L2_VP9_TX_MODE_SELECT, "TX_MODE_SELECT"}), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.tx8, + sizeof(__entry->h.tx8), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.tx16, + sizeof(__entry->h.tx16), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.tx32, + sizeof(__entry->h.tx32), + false), + __print_array(__entry->h.skip, + ARRAY_SIZE(__entry->h.skip), + sizeof(__entry->h.skip[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.inter_mode, + sizeof(__entry->h.inter_mode), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.interp_filter, + sizeof(__entry->h.interp_filter), + false), + __print_array(__entry->h.is_inter, + ARRAY_SIZE(__entry->h.is_inter), + sizeof(__entry->h.is_inter[0])), + __print_array(__entry->h.comp_mode, + ARRAY_SIZE(__entry->h.comp_mode), + sizeof(__entry->h.comp_mode[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.single_ref, + sizeof(__entry->h.single_ref), + false), + __print_array(__entry->h.comp_ref, + ARRAY_SIZE(__entry->h.comp_ref), + sizeof(__entry->h.comp_ref[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.y_mode, + sizeof(__entry->h.y_mode), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.uv_mode, + sizeof(__entry->h.uv_mode), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.partition, + sizeof(__entry->h.partition), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp9_compressed_coef_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp9_compressed_hdr, h)), + TP_fast_assign(__entry->h = *h;), + TP_printk("\n coef {%s}", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.coef, + sizeof(__entry->h.coef), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_vp9_mv_probs_tmpl, + TP_PROTO(const struct v4l2_vp9_mv_probs *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_vp9_mv_probs, p)), + TP_fast_assign(__entry->p = *p;), + TP_printk("\n joint %s\n" + "sign %s\n" + "classes {%s}\n" + "class0_bit %s\n" + "bits {%s}\n" + "class0_fr {%s}\n" + "fr {%s}\n" + "class0_hp %s\n" + "hp %s\n", + __print_array(__entry->p.joint, + ARRAY_SIZE(__entry->p.joint), + sizeof(__entry->p.joint[0])), + __print_array(__entry->p.sign, + ARRAY_SIZE(__entry->p.sign), + sizeof(__entry->p.sign[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.classes, + sizeof(__entry->p.classes), + false), + __print_array(__entry->p.class0_bit, + ARRAY_SIZE(__entry->p.class0_bit), + sizeof(__entry->p.class0_bit[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.bits, + sizeof(__entry->p.bits), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.class0_fr, + sizeof(__entry->p.class0_fr), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.fr, + sizeof(__entry->p.fr), + false), + __print_array(__entry->p.class0_hp, + ARRAY_SIZE(__entry->p.class0_hp), + sizeof(__entry->p.class0_hp[0])), + __print_array(__entry->p.hp, + ARRAY_SIZE(__entry->p.hp), + sizeof(__entry->p.hp[0])) + ) +); + +DEFINE_EVENT(v4l2_ctrl_vp9_frame_tmpl, v4l2_ctrl_vp9_frame, + TP_PROTO(const struct v4l2_ctrl_vp9_frame *f), + TP_ARGS(f) +); + +DEFINE_EVENT(v4l2_ctrl_vp9_compressed_hdr_tmpl, v4l2_ctrl_vp9_compressed_hdr, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h) +); + +DEFINE_EVENT(v4l2_ctrl_vp9_compressed_coef_tmpl, v4l2_ctrl_vp9_compressed_coeff, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h) +); + + +DEFINE_EVENT(v4l2_vp9_mv_probs_tmpl, v4l2_vp9_mv_probs, + TP_PROTO(const struct v4l2_vp9_mv_probs *p), + TP_ARGS(p) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-vp9 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-video.c b/drivers/media/test-drivers/visl/visl-video.c new file mode 100644 index 000000000000..b08664dfbe5f --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-video.c @@ -0,0 +1,767 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Contains the driver implementation for the V4L2 stateless interface. + */ + +#include <linux/debugfs.h> +#include <linux/font.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-vmalloc.h> +#include <media/videobuf2-v4l2.h> + +#include "visl-video.h" + +#include "visl.h" +#include "visl-debugfs.h" + +#define MIN_CODED_SZ (1024U * 256U) + +static void visl_set_current_codec(struct visl_ctx *ctx) +{ + u32 fourcc = ctx->coded_fmt.fmt.pix_mp.pixelformat; + + switch (fourcc) { + case V4L2_PIX_FMT_FWHT_STATELESS: + ctx->current_codec = VISL_CODEC_FWHT; + break; + case V4L2_PIX_FMT_MPEG2_SLICE: + ctx->current_codec = VISL_CODEC_MPEG2; + break; + case V4L2_PIX_FMT_VP8_FRAME: + ctx->current_codec = VISL_CODEC_VP8; + break; + case V4L2_PIX_FMT_VP9_FRAME: + ctx->current_codec = VISL_CODEC_VP9; + break; + case V4L2_PIX_FMT_H264_SLICE: + ctx->current_codec = VISL_CODEC_H264; + break; + case V4L2_PIX_FMT_HEVC_SLICE: + ctx->current_codec = VISL_CODEC_HEVC; + break; + default: + dprintk(ctx->dev, "Warning: unsupported fourcc: %d\n", fourcc); + ctx->current_codec = VISL_CODEC_NONE; + break; + } +} + +static void visl_print_fmt(struct visl_ctx *ctx, const struct v4l2_format *f) +{ + const struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + u32 i; + + dprintk(ctx->dev, "width: %d\n", pix_mp->width); + dprintk(ctx->dev, "height: %d\n", pix_mp->height); + dprintk(ctx->dev, "pixelformat: %c%c%c%c\n", + pix_mp->pixelformat, + (pix_mp->pixelformat >> 8) & 0xff, + (pix_mp->pixelformat >> 16) & 0xff, + (pix_mp->pixelformat >> 24) & 0xff); + + dprintk(ctx->dev, "field: %d\n", pix_mp->field); + dprintk(ctx->dev, "colorspace: %d\n", pix_mp->colorspace); + dprintk(ctx->dev, "num_planes: %d\n", pix_mp->num_planes); + dprintk(ctx->dev, "flags: %d\n", pix_mp->flags); + dprintk(ctx->dev, "quantization: %d\n", pix_mp->quantization); + dprintk(ctx->dev, "xfer_func: %d\n", pix_mp->xfer_func); + + for (i = 0; i < pix_mp->num_planes; i++) { + dprintk(ctx->dev, + "plane[%d]: sizeimage: %d\n", i, pix_mp->plane_fmt[i].sizeimage); + dprintk(ctx->dev, + "plane[%d]: bytesperline: %d\n", i, pix_mp->plane_fmt[i].bytesperline); + } +} + +static int visl_tpg_init(struct visl_ctx *ctx) +{ + const struct font_desc *font; + const char *font_name = "VGA8x16"; + int ret; + u32 width = ctx->decoded_fmt.fmt.pix_mp.width; + u32 height = ctx->decoded_fmt.fmt.pix_mp.height; + struct v4l2_pix_format_mplane *f = &ctx->decoded_fmt.fmt.pix_mp; + + tpg_free(&ctx->tpg); + + font = find_font(font_name); + if (font) { + tpg_init(&ctx->tpg, width, height); + + ret = tpg_alloc(&ctx->tpg, width); + if (ret) + goto err_alloc; + + tpg_set_font(font->data); + ret = tpg_s_fourcc(&ctx->tpg, + f->pixelformat); + + if (!ret) + goto err_fourcc; + + tpg_reset_source(&ctx->tpg, width, height, f->field); + + tpg_s_pattern(&ctx->tpg, TPG_PAT_75_COLORBAR); + + tpg_s_field(&ctx->tpg, f->field, false); + tpg_s_colorspace(&ctx->tpg, f->colorspace); + tpg_s_ycbcr_enc(&ctx->tpg, f->ycbcr_enc); + tpg_s_quantization(&ctx->tpg, f->quantization); + tpg_s_xfer_func(&ctx->tpg, f->xfer_func); + } else { + v4l2_err(&ctx->dev->v4l2_dev, + "Font %s not found\n", font_name); + + return -EINVAL; + } + + dprintk(ctx->dev, "Initialized the V4L2 test pattern generator, w=%d, h=%d, max_w=%d\n", + width, height, width); + + return 0; +err_alloc: + return ret; +err_fourcc: + tpg_free(&ctx->tpg); + return ret; +} + +static const u32 visl_decoded_fmts[] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, +}; + +const struct visl_coded_format_desc visl_coded_fmts[] = { + { + .pixelformat = V4L2_PIX_FMT_FWHT_STATELESS, + .frmsize = { + .min_width = 640, + .max_width = 4096, + .step_width = 1, + .min_height = 360, + .max_height = 2160, + .step_height = 1, + }, + .ctrls = &visl_fwht_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_MPEG2_SLICE, + .frmsize = { + .min_width = 16, + .max_width = 1920, + .step_width = 1, + .min_height = 16, + .max_height = 1152, + .step_height = 1, + }, + .ctrls = &visl_mpeg2_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_VP8_FRAME, + .frmsize = { + .min_width = 64, + .max_width = 16383, + .step_width = 1, + .min_height = 64, + .max_height = 16383, + .step_height = 1, + }, + .ctrls = &visl_vp8_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_VP9_FRAME, + .frmsize = { + .min_width = 64, + .max_width = 8192, + .step_width = 1, + .min_height = 64, + .max_height = 4352, + .step_height = 1, + }, + .ctrls = &visl_vp9_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_H264_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 4096, + .step_width = 1, + .min_height = 64, + .max_height = 2304, + .step_height = 1, + }, + .ctrls = &visl_h264_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_HEVC_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 4096, + .step_width = 1, + .min_height = 64, + .max_height = 2304, + .step_height = 1, + }, + .ctrls = &visl_hevc_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, +}; + +const size_t num_coded_fmts = ARRAY_SIZE(visl_coded_fmts); + +static const struct visl_coded_format_desc* +visl_find_coded_fmt_desc(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(visl_coded_fmts); i++) { + if (visl_coded_fmts[i].pixelformat == fourcc) + return &visl_coded_fmts[i]; + } + + return NULL; +} + +static void visl_init_fmt(struct v4l2_format *f, u32 fourcc) +{ memset(f, 0, sizeof(*f)); + f->fmt.pix_mp.pixelformat = fourcc; + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_DEFAULT; + f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static void visl_reset_coded_fmt(struct visl_ctx *ctx) +{ + struct v4l2_format *f = &ctx->coded_fmt; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + + ctx->coded_format_desc = &visl_coded_fmts[0]; + visl_init_fmt(f, ctx->coded_format_desc->pixelformat); + + f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + f->fmt.pix_mp.width = ctx->coded_format_desc->frmsize.min_width; + f->fmt.pix_mp.height = ctx->coded_format_desc->frmsize.min_height; + + pix_mp->num_planes = 1; + pix_mp->plane_fmt[0].sizeimage = pix_mp->width * pix_mp->height * 8; + + dprintk(ctx->dev, "OUTPUT format was set to:\n"); + visl_print_fmt(ctx, &ctx->coded_fmt); + + visl_set_current_codec(ctx); +} + +static int visl_reset_decoded_fmt(struct visl_ctx *ctx) +{ + struct v4l2_format *f = &ctx->decoded_fmt; + u32 decoded_fmt = ctx->coded_format_desc[0].decoded_fmts[0]; + + visl_init_fmt(f, decoded_fmt); + + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + v4l2_fill_pixfmt_mp(&f->fmt.pix_mp, + ctx->coded_format_desc->decoded_fmts[0], + ctx->coded_fmt.fmt.pix_mp.width, + ctx->coded_fmt.fmt.pix_mp.height); + + dprintk(ctx->dev, "CAPTURE format was set to:\n"); + visl_print_fmt(ctx, &ctx->decoded_fmt); + + return visl_tpg_init(ctx); +} + +int visl_set_default_format(struct visl_ctx *ctx) +{ + visl_reset_coded_fmt(ctx); + return visl_reset_decoded_fmt(ctx); +} + +static struct visl_q_data *get_q_data(struct visl_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return &ctx->q_data[V4L2_M2M_SRC]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + return &ctx->q_data[V4L2_M2M_DST]; + default: + break; + } + return NULL; +} + +static int visl_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, VISL_NAME, sizeof(cap->driver)); + strscpy(cap->card, VISL_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", VISL_NAME); + + return 0; +} + +static int visl_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + + if (f->index >= ctx->coded_format_desc->num_decoded_fmts) + return -EINVAL; + + f->pixelformat = ctx->coded_format_desc->decoded_fmts[f->index]; + return 0; +} + +static int visl_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(visl_coded_fmts)) + return -EINVAL; + + f->pixelformat = visl_coded_fmts[f->index].pixelformat; + return 0; +} + +static int visl_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + *f = ctx->decoded_fmt; + + return 0; +} + +static int visl_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + + *f = ctx->coded_fmt; + return 0; +} + +static int visl_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct visl_ctx *ctx = visl_file_to_ctx(file); + const struct visl_coded_format_desc *coded_desc; + unsigned int i; + + coded_desc = ctx->coded_format_desc; + + for (i = 0; i < coded_desc->num_decoded_fmts; i++) { + if (coded_desc->decoded_fmts[i] == pix_mp->pixelformat) + break; + } + + if (i == coded_desc->num_decoded_fmts) + pix_mp->pixelformat = coded_desc->decoded_fmts[0]; + + v4l2_apply_frmsize_constraints(&pix_mp->width, + &pix_mp->height, + &coded_desc->frmsize); + + v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, + pix_mp->width, pix_mp->height); + + pix_mp->field = V4L2_FIELD_NONE; + + return 0; +} + +static int visl_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct visl_coded_format_desc *coded_desc; + + coded_desc = visl_find_coded_fmt_desc(pix_mp->pixelformat); + if (!coded_desc) { + pix_mp->pixelformat = visl_coded_fmts[0].pixelformat; + coded_desc = &visl_coded_fmts[0]; + } + + v4l2_apply_frmsize_constraints(&pix_mp->width, + &pix_mp->height, + &coded_desc->frmsize); + + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = 1; + + if (pix_mp->plane_fmt[0].sizeimage == 0) + pix_mp->plane_fmt[0].sizeimage = max(MIN_CODED_SZ, + pix_mp->width * pix_mp->height * 3); + + return 0; +} + +static int visl_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + const struct visl_coded_format_desc *desc; + struct vb2_queue *peer_vq; + int ret; + + peer_vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (vb2_is_busy(peer_vq)) + return -EBUSY; + + dprintk(ctx->dev, "Trying to set the OUTPUT format to:\n"); + visl_print_fmt(ctx, f); + + ret = visl_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + desc = visl_find_coded_fmt_desc(f->fmt.pix_mp.pixelformat); + ctx->coded_format_desc = desc; + ctx->coded_fmt = *f; + + ret = visl_reset_decoded_fmt(ctx); + if (ret) + return ret; + + ctx->decoded_fmt.fmt.pix_mp.colorspace = f->fmt.pix_mp.colorspace; + ctx->decoded_fmt.fmt.pix_mp.xfer_func = f->fmt.pix_mp.xfer_func; + ctx->decoded_fmt.fmt.pix_mp.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + ctx->decoded_fmt.fmt.pix_mp.quantization = f->fmt.pix_mp.quantization; + + dprintk(ctx->dev, "OUTPUT format was set to:\n"); + visl_print_fmt(ctx, &ctx->coded_fmt); + + visl_set_current_codec(ctx); + return 0; +} + +static int visl_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + int ret; + + dprintk(ctx->dev, "Trying to set the CAPTURE format to:\n"); + visl_print_fmt(ctx, f); + + ret = visl_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ctx->decoded_fmt = *f; + + dprintk(ctx->dev, "CAPTURE format was set to:\n"); + visl_print_fmt(ctx, &ctx->decoded_fmt); + + visl_tpg_init(ctx); + return 0; +} + +static int visl_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + const struct visl_coded_format_desc *fmt; + struct visl_ctx *ctx = visl_file_to_ctx(file); + + if (fsize->index != 0) + return -EINVAL; + + fmt = visl_find_coded_fmt_desc(fsize->pixel_format); + if (!fmt) { + dprintk(ctx->dev, + "Unsupported format for the OUTPUT queue: %d\n", + fsize->pixel_format); + + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = fmt->frmsize; + return 0; +} + +const struct v4l2_ioctl_ops visl_ioctl_ops = { + .vidioc_querycap = visl_querycap, + .vidioc_enum_framesizes = visl_enum_framesizes, + + .vidioc_enum_fmt_vid_cap = visl_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = visl_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = visl_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = visl_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = visl_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out_mplane = visl_g_fmt_vid_out, + .vidioc_try_fmt_vid_out_mplane = visl_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = visl_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int visl_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_format *f; + u32 i; + char *qname; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + f = &ctx->coded_fmt; + qname = "Output"; + } else { + f = &ctx->decoded_fmt; + qname = "Capture"; + } + + if (*num_planes) { + if (*num_planes != f->fmt.pix_mp.num_planes) + return -EINVAL; + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) { + if (sizes[i] < f->fmt.pix_mp.plane_fmt[i].sizeimage) + return -EINVAL; + } + } else { + *num_planes = f->fmt.pix_mp.num_planes; + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) + sizes[i] = f->fmt.pix_mp.plane_fmt[i].sizeimage; + } + + dprintk(ctx->dev, "%s: %d buffer(s) requested, num_planes=%d.\n", + qname, *nbuffers, *num_planes); + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) + dprintk(ctx->dev, "plane[%d].sizeimage=%d\n", + i, f->fmt.pix_mp.plane_fmt[i].sizeimage); + + return 0; +} + +static void visl_queue_cleanup(struct vb2_queue *vq, u32 state) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf; + + dprintk(ctx->dev, "Cleaning up queues\n"); + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (!vbuf) + break; + + v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, + &ctx->hdl); + dprintk(ctx->dev, "Marked request %p as complete\n", + vbuf->vb2_buf.req_obj.req); + + v4l2_m2m_buf_done(vbuf, state); + dprintk(ctx->dev, + "Marked buffer %llu as done, state is %d\n", + vbuf->vb2_buf.timestamp, + state); + } +} + +static int visl_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static int visl_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + u32 plane_sz = vb2_plane_size(vb, 0); + struct v4l2_pix_format *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + pix_fmt = &ctx->coded_fmt.fmt.pix; + } else { + pix_fmt = &ctx->decoded_fmt.fmt.pix; + vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage); + } + + if (plane_sz < pix_fmt->sizeimage) { + v4l2_err(&ctx->dev->v4l2_dev, "plane[0] size is %d, sizeimage is %d\n", + plane_sz, pix_fmt->sizeimage); + return -EINVAL; + } + + return 0; +} + +static int visl_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + struct visl_q_data *q_data = get_q_data(ctx, vq->type); + int rc = 0; + + if (!q_data) { + rc = -EINVAL; + goto err; + } + + q_data->sequence = 0; + + if (V4L2_TYPE_IS_CAPTURE(vq->type)) { + ctx->capture_streamon_jiffies = get_jiffies_64(); + return 0; + } + + if (WARN_ON(!ctx->coded_format_desc)) { + rc = -EINVAL; + goto err; + } + + return 0; + +err: + visl_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); + return rc; +} + +static void visl_stop_streaming(struct vb2_queue *vq) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + + dprintk(ctx->dev, "Stop streaming\n"); + visl_queue_cleanup(vq, VB2_BUF_STATE_ERROR); + + if (!keep_bitstream_buffers) + visl_debugfs_clear_bitstream(ctx->dev); +} + +static void visl_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct visl_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void visl_buf_request_complete(struct vb2_buffer *vb) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); +} + +const struct vb2_ops visl_qops = { + .queue_setup = visl_queue_setup, + .buf_out_validate = visl_buf_out_validate, + .buf_prepare = visl_buf_prepare, + .buf_queue = visl_buf_queue, + .start_streaming = visl_start_streaming, + .stop_streaming = visl_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_request_complete = visl_buf_request_complete, +}; + +int visl_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct visl_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &visl_qops; + src_vq->mem_ops = &vb2_vmalloc_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->vb_mutex; + src_vq->supports_requests = true; + src_vq->subsystem_flags |= VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &visl_qops; + dst_vq->mem_ops = &vb2_vmalloc_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->vb_mutex; + + return vb2_queue_init(dst_vq); +} + +int visl_request_validate(struct media_request *req) +{ + struct media_request_object *obj; + struct visl_ctx *ctx = NULL; + unsigned int count; + + list_for_each_entry(obj, &req->objects, list) { + struct vb2_buffer *vb; + + if (vb2_request_object_is_buffer(obj)) { + vb = container_of(obj, struct vb2_buffer, req_obj); + ctx = vb2_get_drv_priv(vb->vb2_queue); + + break; + } + } + + if (!ctx) + return -ENOENT; + + count = vb2_request_buffer_cnt(req); + if (!count) { + v4l2_err(&ctx->dev->v4l2_dev, + "No buffer was provided with the request\n"); + return -ENOENT; + } else if (count > 1) { + v4l2_err(&ctx->dev->v4l2_dev, + "More than one buffer was provided with the request\n"); + return -EINVAL; + } + + return vb2_request_validate(req); +} diff --git a/drivers/media/test-drivers/visl/visl-video.h b/drivers/media/test-drivers/visl/visl-video.h new file mode 100644 index 000000000000..27ad70a558db --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-video.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Contains the driver implementation for the V4L2 stateless interface. + */ + +#ifndef _VISL_VIDEO_H_ +#define _VISL_VIDEO_H_ +#include <media/v4l2-mem2mem.h> + +#include "visl.h" + +extern const struct v4l2_ioctl_ops visl_ioctl_ops; + +extern const struct visl_ctrls visl_fwht_ctrls; +extern const struct visl_ctrls visl_mpeg2_ctrls; +extern const struct visl_ctrls visl_vp8_ctrls; +extern const struct visl_ctrls visl_vp9_ctrls; +extern const struct visl_ctrls visl_h264_ctrls; +extern const struct visl_ctrls visl_hevc_ctrls; + +int visl_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); + +int visl_set_default_format(struct visl_ctx *ctx); +int visl_request_validate(struct media_request *req); + +#endif /* _VISL_VIDEO_H_ */ diff --git a/drivers/media/test-drivers/visl/visl.h b/drivers/media/test-drivers/visl/visl.h new file mode 100644 index 000000000000..31639f2e593d --- /dev/null +++ b/drivers/media/test-drivers/visl/visl.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A virtual stateless device for stateless uAPI development purposes. + * + * This tool's objective is to help the development and testing of userspace + * applications that use the V4L2 stateless API to decode media. + * + * A userspace implementation can use visl to run a decoding loop even when no + * hardware is available or when the kernel uAPI for the codec has not been + * upstreamed yet. This can reveal bugs at an early stage. + * + * This driver can also trace the contents of the V4L2 controls submitted to it. + * It can also dump the contents of the vb2 buffers through a debugfs + * interface. This is in many ways similar to the tracing infrastructure + * available for other popular encode/decode APIs out there and can help develop + * a userspace application by using another (working) one as a reference. + * + * Note that no actual decoding of video frames is performed by visl. The V4L2 + * test pattern generator is used to write various debug information to the + * capture buffers instead. + * + * Copyright (C) 2022 Collabora, Ltd. + * + * Based on the vim2m driver, that is: + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + * + * Based on the vicodec driver, that is: + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#ifndef _VISL_H_ +#define _VISL_H_ + +#include <linux/debugfs.h> +#include <linux/list.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/tpg/v4l2-tpg.h> + +#define VISL_NAME "visl" +#define VISL_M2M_NQUEUES 2 + +#define TPG_STR_BUF_SZ 2048 + +extern unsigned int visl_transtime_ms; + +struct visl_ctrls { + const struct visl_ctrl_desc *ctrls; + unsigned int num_ctrls; +}; + +struct visl_coded_format_desc { + u32 pixelformat; + struct v4l2_frmsize_stepwise frmsize; + const struct visl_ctrls *ctrls; + unsigned int num_decoded_fmts; + const u32 *decoded_fmts; +}; + +extern const struct visl_coded_format_desc visl_coded_fmts[]; +extern const size_t num_coded_fmts; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +extern unsigned int visl_debug; +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, visl_debug, &(dev)->v4l2_dev, "%s: " fmt, __func__, ## arg) + +extern int visl_dprintk_frame_start; +extern unsigned int visl_dprintk_nframes; +extern bool keep_bitstream_buffers; +extern int bitstream_trace_frame_start; +extern unsigned int bitstream_trace_nframes; + +#define frame_dprintk(dev, current, fmt, arg...) \ + do { \ + if (visl_dprintk_frame_start > -1 && \ + (current) >= visl_dprintk_frame_start && \ + (current) < visl_dprintk_frame_start + visl_dprintk_nframes) \ + dprintk(dev, fmt, ## arg); \ + } while (0) \ + +struct visl_q_data { + unsigned int sequence; +}; + +struct visl_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device mdev; +#endif + + struct mutex dev_mutex; + + struct v4l2_m2m_dev *m2m_dev; + +#ifdef CONFIG_VISL_DEBUGFS + struct dentry *debugfs_root; + struct dentry *bitstream_debugfs; + struct list_head bitstream_blobs; + + /* Protects the "blob" list */ + struct mutex bitstream_lock; +#endif +}; + +enum visl_codec { + VISL_CODEC_NONE, + VISL_CODEC_FWHT, + VISL_CODEC_MPEG2, + VISL_CODEC_VP8, + VISL_CODEC_VP9, + VISL_CODEC_H264, + VISL_CODEC_HEVC, +}; + +struct visl_blob { + struct list_head list; + struct dentry *dentry; + struct debugfs_blob_wrapper blob; +}; + +struct visl_ctx { + struct v4l2_fh fh; + struct visl_dev *dev; + struct v4l2_ctrl_handler hdl; + + struct mutex vb_mutex; + + struct visl_q_data q_data[VISL_M2M_NQUEUES]; + enum visl_codec current_codec; + + const struct visl_coded_format_desc *coded_format_desc; + + struct v4l2_format coded_fmt; + struct v4l2_format decoded_fmt; + + struct tpg_data tpg; + u64 capture_streamon_jiffies; + char *tpg_str_buf; +}; + +struct visl_ctrl_desc { + struct v4l2_ctrl_config cfg; +}; + +static inline struct visl_ctx *visl_file_to_ctx(struct file *file) +{ + return container_of(file->private_data, struct visl_ctx, fh); +} + +static inline struct visl_ctx *visl_v4l2fh_to_ctx(struct v4l2_fh *v4l2_fh) +{ + return container_of(v4l2_fh, struct visl_ctx, fh); +} + +void *visl_find_control_data(struct visl_ctx *ctx, u32 id); +struct v4l2_ctrl *visl_find_control(struct visl_ctx *ctx, u32 id); +u32 visl_control_num_elems(struct visl_ctx *ctx, u32 id); + +#endif /* _VISL_H_ */ diff --git a/drivers/media/test-drivers/vivid/vivid-ctrls.c b/drivers/media/test-drivers/vivid/vivid-ctrls.c index 92b1a7598470..f2b20e25a7a4 100644 --- a/drivers/media/test-drivers/vivid/vivid-ctrls.c +++ b/drivers/media/test-drivers/vivid/vivid-ctrls.c @@ -36,6 +36,8 @@ #define VIVID_CID_RO_INTEGER (VIVID_CID_CUSTOM_BASE + 12) #define VIVID_CID_U32_DYN_ARRAY (VIVID_CID_CUSTOM_BASE + 13) #define VIVID_CID_U8_PIXEL_ARRAY (VIVID_CID_CUSTOM_BASE + 14) +#define VIVID_CID_S32_ARRAY (VIVID_CID_CUSTOM_BASE + 15) +#define VIVID_CID_S64_ARRAY (VIVID_CID_CUSTOM_BASE + 16) #define VIVID_CID_VIVID_BASE (0x00f00000 | 0xf000) #define VIVID_CID_VIVID_CLASS (0x00f00000 | 1) @@ -241,6 +243,30 @@ static const struct v4l2_ctrl_config vivid_ctrl_u8_pixel_array = { .dims = { 640 / PIXEL_ARRAY_DIV, 360 / PIXEL_ARRAY_DIV }, }; +static const struct v4l2_ctrl_config vivid_ctrl_s32_array = { + .ops = &vivid_user_gen_ctrl_ops, + .id = VIVID_CID_S32_ARRAY, + .name = "S32 2 Element Array", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = 2, + .min = -10, + .max = 10, + .step = 1, + .dims = { 2 }, +}; + +static const struct v4l2_ctrl_config vivid_ctrl_s64_array = { + .ops = &vivid_user_gen_ctrl_ops, + .id = VIVID_CID_S64_ARRAY, + .name = "S64 5 Element Array", + .type = V4L2_CTRL_TYPE_INTEGER64, + .def = 4, + .min = -10, + .max = 10, + .step = 1, + .dims = { 5 }, +}; + static const char * const vivid_ctrl_menu_strings[] = { "Menu Item 0 (Skipped)", "Menu Item 1", @@ -1656,6 +1682,8 @@ int vivid_create_controls(struct vivid_dev *dev, bool show_ccs_cap, v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_u16_matrix, NULL); v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_u8_4d_array, NULL); dev->pixel_array = v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_u8_pixel_array, NULL); + v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_s32_array, NULL); + v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_s64_array, NULL); if (dev->has_vid_cap) { /* Image Processing Controls */ diff --git a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c index a141369a7a63..70a4024d461e 100644 --- a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c +++ b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c @@ -194,7 +194,6 @@ static void vivid_vbi_gen_set_time_of_day(u8 *packet) for (checksum = i = 0; i <= 8; i++) checksum += packet[i] & 0x7f; packet[9] = calc_parity(0x100 - checksum); - checksum = 0; packet[10] = calc_parity(0x07); packet[11] = calc_parity(0x04); if (sys_tz.tz_minuteswest >= 0) diff --git a/drivers/media/test-drivers/vivid/vivid-vid-cap.c b/drivers/media/test-drivers/vivid/vivid-vid-cap.c index 11620eaf941e..c0999581c599 100644 --- a/drivers/media/test-drivers/vivid/vivid-vid-cap.c +++ b/drivers/media/test-drivers/vivid/vivid-vid-cap.c @@ -973,6 +973,7 @@ int vivid_vid_cap_s_selection(struct file *file, void *fh, struct v4l2_selection if (dev->has_compose_cap) { v4l2_rect_set_min_size(compose, &min_rect); v4l2_rect_set_max_size(compose, &max_rect); + v4l2_rect_map_inside(compose, &fmt); } dev->fmt_cap_rect = fmt; tpg_s_buf_height(&dev->tpg, fmt.height); diff --git a/drivers/media/tuners/e4000.c b/drivers/media/tuners/e4000.c index 61ae884ea59a..7c269f3159ef 100644 --- a/drivers/media/tuners/e4000.c +++ b/drivers/media/tuners/e4000.c @@ -609,8 +609,7 @@ static const struct dvb_tuner_ops e4000_dvb_tuner_ops = { .get_if_frequency = e4000_dvb_get_if_frequency, }; -static int e4000_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int e4000_probe(struct i2c_client *client) { struct e4000_dev *dev; struct e4000_config *cfg = client->dev.platform_data; @@ -730,7 +729,7 @@ static struct i2c_driver e4000_driver = { .name = "e4000", .suppress_bind_attrs = true, }, - .probe = e4000_probe, + .probe_new = e4000_probe, .remove = e4000_remove, .id_table = e4000_id_table, }; diff --git a/drivers/media/tuners/fc2580.c b/drivers/media/tuners/fc2580.c index f30932e1a0f3..3cd8279f4f2e 100644 --- a/drivers/media/tuners/fc2580.c +++ b/drivers/media/tuners/fc2580.c @@ -506,8 +506,7 @@ static struct v4l2_subdev *fc2580_get_v4l2_subdev(struct i2c_client *client) return NULL; } -static int fc2580_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int fc2580_probe(struct i2c_client *client) { struct fc2580_dev *dev; struct fc2580_platform_data *pdata = client->dev.platform_data; @@ -611,7 +610,7 @@ static struct i2c_driver fc2580_driver = { .name = "fc2580", .suppress_bind_attrs = true, }, - .probe = fc2580_probe, + .probe_new = fc2580_probe, .remove = fc2580_remove, .id_table = fc2580_id_table, }; diff --git a/drivers/media/tuners/m88rs6000t.c b/drivers/media/tuners/m88rs6000t.c index e32e3e9daa15..7d172a5a66d9 100644 --- a/drivers/media/tuners/m88rs6000t.c +++ b/drivers/media/tuners/m88rs6000t.c @@ -573,8 +573,7 @@ static const struct dvb_tuner_ops m88rs6000t_tuner_ops = { .get_rf_strength = m88rs6000t_get_rf_strength, }; -static int m88rs6000t_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int m88rs6000t_probe(struct i2c_client *client) { struct m88rs6000t_config *cfg = client->dev.platform_data; struct dvb_frontend *fe = cfg->fe; @@ -719,7 +718,7 @@ static struct i2c_driver m88rs6000t_driver = { .driver = { .name = "m88rs6000t", }, - .probe = m88rs6000t_probe, + .probe_new = m88rs6000t_probe, .remove = m88rs6000t_remove, .id_table = m88rs6000t_id, }; diff --git a/drivers/media/tuners/mt2060.c b/drivers/media/tuners/mt2060.c index 322c806228a5..e5d86874adb3 100644 --- a/drivers/media/tuners/mt2060.c +++ b/drivers/media/tuners/mt2060.c @@ -442,8 +442,7 @@ struct dvb_frontend * mt2060_attach(struct dvb_frontend *fe, struct i2c_adapter } EXPORT_SYMBOL(mt2060_attach); -static int mt2060_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mt2060_probe(struct i2c_client *client) { struct mt2060_platform_data *pdata = client->dev.platform_data; struct dvb_frontend *fe; @@ -525,7 +524,7 @@ static struct i2c_driver mt2060_driver = { .name = "mt2060", .suppress_bind_attrs = true, }, - .probe = mt2060_probe, + .probe_new = mt2060_probe, .remove = mt2060_remove, .id_table = mt2060_id_table, }; diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c index 6422056185a9..c35442a77ae5 100644 --- a/drivers/media/tuners/mxl301rf.c +++ b/drivers/media/tuners/mxl301rf.c @@ -283,8 +283,7 @@ static const struct dvb_tuner_ops mxl301rf_ops = { }; -static int mxl301rf_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mxl301rf_probe(struct i2c_client *client) { struct mxl301rf_state *state; struct mxl301rf_config *cfg; @@ -327,7 +326,7 @@ static struct i2c_driver mxl301rf_driver = { .driver = { .name = "mxl301rf", }, - .probe = mxl301rf_probe, + .probe_new = mxl301rf_probe, .remove = mxl301rf_remove, .id_table = mxl301rf_id, }; diff --git a/drivers/media/tuners/mxl5005s.c b/drivers/media/tuners/mxl5005s.c index ab4c43df9d18..3a509038c8df 100644 --- a/drivers/media/tuners/mxl5005s.c +++ b/drivers/media/tuners/mxl5005s.c @@ -3637,7 +3637,7 @@ static u16 MXL_GetCHRegister_ZeroIF(struct dvb_frontend *fe, u8 *RegNum, u16 status = 0; int i; - u8 RegAddr[] = {43, 136}; + static const u8 RegAddr[] = {43, 136}; *count = ARRAY_SIZE(RegAddr); diff --git a/drivers/media/tuners/qm1d1b0004.c b/drivers/media/tuners/qm1d1b0004.c index 9cba0893207c..0b6f750c54ad 100644 --- a/drivers/media/tuners/qm1d1b0004.c +++ b/drivers/media/tuners/qm1d1b0004.c @@ -197,7 +197,7 @@ static const struct dvb_tuner_ops qm1d1b0004_ops = { }; static int -qm1d1b0004_probe(struct i2c_client *client, const struct i2c_device_id *id) +qm1d1b0004_probe(struct i2c_client *client) { struct dvb_frontend *fe; struct qm1d1b0004_config *cfg; @@ -253,7 +253,7 @@ static struct i2c_driver qm1d1b0004_driver = { .driver = { .name = "qm1d1b0004", }, - .probe = qm1d1b0004_probe, + .probe_new = qm1d1b0004_probe, .remove = qm1d1b0004_remove, .id_table = qm1d1b0004_id, }; diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c index 2d60bf501fb5..f9be7a721d2c 100644 --- a/drivers/media/tuners/qm1d1c0042.c +++ b/drivers/media/tuners/qm1d1c0042.c @@ -401,8 +401,7 @@ static const struct dvb_tuner_ops qm1d1c0042_ops = { }; -static int qm1d1c0042_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int qm1d1c0042_probe(struct i2c_client *client) { struct qm1d1c0042_state *state; struct qm1d1c0042_config *cfg; @@ -444,7 +443,7 @@ static struct i2c_driver qm1d1c0042_driver = { .driver = { .name = "qm1d1c0042", }, - .probe = qm1d1c0042_probe, + .probe_new = qm1d1c0042_probe, .remove = qm1d1c0042_remove, .id_table = qm1d1c0042_id, }; diff --git a/drivers/media/tuners/tda18212.c b/drivers/media/tuners/tda18212.c index eb97711c9c68..5fdf05a97415 100644 --- a/drivers/media/tuners/tda18212.c +++ b/drivers/media/tuners/tda18212.c @@ -173,8 +173,7 @@ static const struct dvb_tuner_ops tda18212_tuner_ops = { .get_if_frequency = tda18212_get_if_frequency, }; -static int tda18212_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda18212_probe(struct i2c_client *client) { struct tda18212_config *cfg = client->dev.platform_data; struct dvb_frontend *fe = cfg->fe; @@ -264,7 +263,7 @@ static struct i2c_driver tda18212_driver = { .driver = { .name = "tda18212", }, - .probe = tda18212_probe, + .probe_new = tda18212_probe, .remove = tda18212_remove, .id_table = tda18212_id, }; diff --git a/drivers/media/tuners/tda18250.c b/drivers/media/tuners/tda18250.c index e404a5afad4c..66ff2d035de7 100644 --- a/drivers/media/tuners/tda18250.c +++ b/drivers/media/tuners/tda18250.c @@ -741,8 +741,7 @@ static const struct dvb_tuner_ops tda18250_ops = { .sleep = tda18250_sleep, }; -static int tda18250_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda18250_probe(struct i2c_client *client) { struct tda18250_config *cfg = client->dev.platform_data; struct dvb_frontend *fe = cfg->fe; @@ -878,7 +877,7 @@ static struct i2c_driver tda18250_driver = { .driver = { .name = "tda18250", }, - .probe = tda18250_probe, + .probe_new = tda18250_probe, .remove = tda18250_remove, .id_table = tda18250_id_table, }; diff --git a/drivers/media/tuners/tua9001.c b/drivers/media/tuners/tua9001.c index d141d000b819..ac38afd3441a 100644 --- a/drivers/media/tuners/tua9001.c +++ b/drivers/media/tuners/tua9001.c @@ -167,8 +167,7 @@ static const struct dvb_tuner_ops tua9001_tuner_ops = { .get_if_frequency = tua9001_get_if_frequency, }; -static int tua9001_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tua9001_probe(struct i2c_client *client) { struct tua9001_dev *dev; struct tua9001_platform_data *pdata = client->dev.platform_data; @@ -256,7 +255,7 @@ static struct i2c_driver tua9001_driver = { .name = "tua9001", .suppress_bind_attrs = true, }, - .probe = tua9001_probe, + .probe_new = tua9001_probe, .remove = tua9001_remove, .id_table = tua9001_id_table, }; diff --git a/drivers/media/usb/au0828/au0828-vbi.c b/drivers/media/usb/au0828/au0828-vbi.c index 97f5e8733c2a..b0333637b747 100644 --- a/drivers/media/usb/au0828/au0828-vbi.c +++ b/drivers/media/usb/au0828/au0828-vbi.c @@ -14,6 +14,7 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> +#include <media/v4l2-mc.h> /* ------------------------------------------------------------------ */ @@ -70,6 +71,7 @@ const struct vb2_ops au0828_vbi_qops = { .queue_setup = vbi_queue_setup, .buf_prepare = vbi_buffer_prepare, .buf_queue = vbi_buffer_queue, + .prepare_streaming = v4l_vb2q_enable_media_source, .start_streaming = au0828_start_analog_streaming, .stop_streaming = au0828_stop_vbi_streaming, .wait_prepare = vb2_ops_wait_prepare, diff --git a/drivers/media/usb/au0828/au0828-video.c b/drivers/media/usb/au0828/au0828-video.c index eb303e94cceb..fd9fc43d47e0 100644 --- a/drivers/media/usb/au0828/au0828-video.c +++ b/drivers/media/usb/au0828/au0828-video.c @@ -915,6 +915,7 @@ static const struct vb2_ops au0828_video_qops = { .queue_setup = queue_setup, .buf_prepare = buffer_prepare, .buf_queue = buffer_queue, + .prepare_streaming = v4l_vb2q_enable_media_source, .start_streaming = au0828_start_analog_streaming, .stop_streaming = au0828_stop_streaming, .wait_prepare = vb2_ops_wait_prepare, diff --git a/drivers/media/usb/dvb-usb/az6027.c b/drivers/media/usb/dvb-usb/az6027.c index cf15988dfb51..7d78ee09be5e 100644 --- a/drivers/media/usb/dvb-usb/az6027.c +++ b/drivers/media/usb/dvb-usb/az6027.c @@ -975,6 +975,10 @@ static int az6027_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], int n if (msg[i].addr == 0x99) { req = 0xBE; index = 0; + if (msg[i].len < 1) { + i = -EOPNOTSUPP; + break; + } value = msg[i].buf[0] & 0x00ff; length = 1; az6027_usb_out_op(d, req, value, index, data, length); diff --git a/drivers/media/usb/dvb-usb/dib0700.h b/drivers/media/usb/dvb-usb/dib0700.h index 2defbd8b6fc1..ac06a9ab2641 100644 --- a/drivers/media/usb/dvb-usb/dib0700.h +++ b/drivers/media/usb/dvb-usb/dib0700.h @@ -72,7 +72,6 @@ int dib0700_set_i2c_speed(struct dvb_usb_device *d, u16 scl_kHz); extern struct i2c_algorithm dib0700_i2c_algo; extern int dib0700_device_count; -extern int dvb_usb_dib0700_ir_proto; extern struct dvb_usb_device_properties dib0700_devices[]; extern struct usb_device_id dib0700_usb_id_table[]; diff --git a/drivers/media/usb/dvb-usb/dib0700_devices.c b/drivers/media/usb/dvb-usb/dib0700_devices.c index 7f8bebfa3e8e..3af594134a6d 100644 --- a/drivers/media/usb/dvb-usb/dib0700_devices.c +++ b/drivers/media/usb/dvb-usb/dib0700_devices.c @@ -2024,13 +2024,6 @@ static struct dib0090_config tfe8096p_dib0090_config = { .force_cband_input = 0, }; -struct dibx090p_adc { - u32 freq; /* RF freq MHz */ - u32 timf; /* New Timf */ - u32 pll_loopdiv; /* New prediv */ - u32 pll_prediv; /* New loopdiv */ -}; - struct dibx090p_best_adc { u32 timf; u32 pll_loopdiv; diff --git a/drivers/media/usb/dvb-usb/dvb-usb-init.c b/drivers/media/usb/dvb-usb/dvb-usb-init.c index 61439c8f33ca..fbf58012becd 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-init.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-init.c @@ -81,7 +81,7 @@ static int dvb_usb_adapter_init(struct dvb_usb_device *d, short *adapter_nrs) ret = dvb_usb_adapter_stream_init(adap); if (ret) - return ret; + goto stream_init_err; ret = dvb_usb_adapter_dvb_init(adap, adapter_nrs); if (ret) @@ -92,7 +92,7 @@ static int dvb_usb_adapter_init(struct dvb_usb_device *d, short *adapter_nrs) goto frontend_init_err; /* use exclusive FE lock if there is multiple shared FEs */ - if (adap->fe_adap[1].fe) + if (adap->fe_adap[1].fe && adap->dvb_adap.mfe_shared < 1) adap->dvb_adap.mfe_shared = 1; d->num_adapters_initialized++; @@ -114,6 +114,8 @@ frontend_init_err: dvb_usb_adapter_dvb_exit(adap); dvb_init_err: dvb_usb_adapter_stream_exit(adap); +stream_init_err: + kfree(adap->priv); return ret; } diff --git a/drivers/media/usb/dvb-usb/m920x.c b/drivers/media/usb/dvb-usb/m920x.c index 548199cd86f6..fea5bcf72a31 100644 --- a/drivers/media/usb/dvb-usb/m920x.c +++ b/drivers/media/usb/dvb-usb/m920x.c @@ -485,14 +485,14 @@ static int m920x_identify_state(struct usb_device *udev, static int m920x_mt352_demod_init(struct dvb_frontend *fe) { int ret; - u8 config[] = { CONFIG, 0x3d }; - u8 clock[] = { CLOCK_CTL, 0x30 }; - u8 reset[] = { RESET, 0x80 }; - u8 adc_ctl[] = { ADC_CTL_1, 0x40 }; - u8 agc[] = { AGC_TARGET, 0x1c, 0x20 }; - u8 sec_agc[] = { 0x69, 0x00, 0xff, 0xff, 0x40, 0xff, 0x00, 0x40, 0x40 }; - u8 unk1[] = { 0x93, 0x1a }; - u8 unk2[] = { 0xb5, 0x7a }; + static const u8 config[] = { CONFIG, 0x3d }; + static const u8 clock[] = { CLOCK_CTL, 0x30 }; + static const u8 reset[] = { RESET, 0x80 }; + static const u8 adc_ctl[] = { ADC_CTL_1, 0x40 }; + static const u8 agc[] = { AGC_TARGET, 0x1c, 0x20 }; + static const u8 sec_agc[] = { 0x69, 0x00, 0xff, 0xff, 0x40, 0xff, 0x00, 0x40, 0x40 }; + static const u8 unk1[] = { 0x93, 0x1a }; + static const u8 unk2[] = { 0xb5, 0x7a }; deb("Demod init!\n"); diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c index 185e89c18d68..9fce59979e3b 100644 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c @@ -1204,6 +1204,12 @@ static int em28178_dvb_init_pctv_461e(struct em28xx *dev) /* attach SEC */ a8293_pdata.dvb_frontend = dvb->fe[0]; + /* + * 461e has a tendency to have vIN undervoltage troubles. + * Slew mitigates this. + */ + a8293_pdata.volt_slew_nanos_per_mv = 20; + dvb->i2c_client_sec = dvb_module_probe("a8293", NULL, &dev->i2c_adap[dev->def_i2c_bus], 0x08, &a8293_pdata); diff --git a/drivers/media/usb/go7007/s2250-board.c b/drivers/media/usb/go7007/s2250-board.c index 2f45188bf9d4..29dfcc6d0b0a 100644 --- a/drivers/media/usb/go7007/s2250-board.c +++ b/drivers/media/usb/go7007/s2250-board.c @@ -494,8 +494,7 @@ static const struct v4l2_subdev_ops s2250_ops = { /* --------------------------------------------------------------------------*/ -static int s2250_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int s2250_probe(struct i2c_client *client) { struct i2c_client *audio; struct i2c_adapter *adapter = client->adapter; @@ -621,7 +620,7 @@ static struct i2c_driver s2250_driver = { .driver = { .name = "s2250", }, - .probe = s2250_probe, + .probe_new = s2250_probe, .remove = s2250_remove, .id_table = s2250_id, }; diff --git a/drivers/media/usb/pwc/pwc-uncompress.c b/drivers/media/usb/pwc/pwc-uncompress.c index faf44cdeb268..cf2591a9675c 100644 --- a/drivers/media/usb/pwc/pwc-uncompress.c +++ b/drivers/media/usb/pwc/pwc-uncompress.c @@ -39,7 +39,7 @@ int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf) * first 3 bytes is filled (Nala case). We can * determine this using the type of the webcam */ memcpy(raw_frame->cmd, pdev->cmd_buf, 4); - memcpy(raw_frame+1, yuv, pdev->frame_size); + memcpy(raw_frame->rawframe, yuv, pdev->frame_size); vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, struct_size(raw_frame, rawframe, pdev->frame_size)); return 0; diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 215fb483efb0..e4bcb5011360 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1266,12 +1266,9 @@ static int uvc_gpio_parse(struct uvc_device *dev) return PTR_ERR_OR_ZERO(gpio_privacy); irq = gpiod_to_irq(gpio_privacy); - if (irq < 0) { - if (irq != EPROBE_DEFER) - dev_err(&dev->udev->dev, - "No IRQ for privacy GPIO (%d)\n", irq); - return irq; - } + if (irq < 0) + return dev_err_probe(&dev->udev->dev, irq, + "No IRQ for privacy GPIO\n"); unit = uvc_alloc_entity(UVC_EXT_GPIO_UNIT, UVC_EXT_GPIO_UNIT_ID, 0, 1); if (!unit) diff --git a/drivers/media/v4l2-core/tuner-core.c b/drivers/media/v4l2-core/tuner-core.c index 33162dc1daf6..1c0d23c52203 100644 --- a/drivers/media/v4l2-core/tuner-core.c +++ b/drivers/media/v4l2-core/tuner-core.c @@ -614,7 +614,6 @@ static void tuner_lookup(struct i2c_adapter *adap, *tuner_probe - Probes the existing tuners on an I2C bus * * @client: i2c_client descriptor - * @id: not used * * This routine probes for tuners at the expected I2C addresses. On most * cases, if a device answers to a given I2C address, it assumes that the @@ -625,8 +624,7 @@ static void tuner_lookup(struct i2c_adapter *adap, * During client attach, set_type is called by adapter's attach_inform callback. * set_type must then be completed by tuner_probe. */ -static int tuner_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tuner_probe(struct i2c_client *client) { struct tuner *t; struct tuner *radio; @@ -1413,7 +1411,7 @@ static struct i2c_driver tuner_driver = { .name = "tuner", .pm = &tuner_pm_ops, }, - .probe = tuner_probe, + .probe_new = tuner_probe, .remove = tuner_remove, .command = tuner_command, .id_table = tuner_id, diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 0dab1d7b90f0..29169170880a 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -1827,7 +1827,7 @@ struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, else if (type == V4L2_CTRL_TYPE_INTEGER_MENU) qmenu_int = v4l2_ctrl_get_int_menu(id, &qmenu_int_len); - if ((!qmenu && !qmenu_int) || (qmenu_int && max > qmenu_int_len)) { + if ((!qmenu && !qmenu_int) || (qmenu_int && max >= qmenu_int_len)) { handler_set_err(hdl, -EINVAL); return NULL; } diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index e22921e7ea61..564fedee2c88 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1043,6 +1043,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_UNIT_CELL_SIZE: return "Unit Cell Size"; case V4L2_CID_CAMERA_ORIENTATION: return "Camera Orientation"; case V4L2_CID_CAMERA_SENSOR_ROTATION: return "Camera Sensor Rotation"; + case V4L2_CID_HDR_SENSOR_MODE: return "HDR Sensor Mode"; /* FM Radio Modulator controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ @@ -1370,6 +1371,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_STATELESS_H264_START_CODE: case V4L2_CID_CAMERA_ORIENTATION: case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE: + case V4L2_CID_HDR_SENSOR_MODE: *type = V4L2_CTRL_TYPE_MENU; break; case V4L2_CID_LINK_FREQ: diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index 3d85a8600f57..3d9533c1b202 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -298,10 +298,25 @@ v4l2_fwnode_endpoint_parse_parallel_bus(struct fwnode_handle *fwnode, if (!fwnode_property_read_u32(fwnode, "pclk-sample", &v)) { flags &= ~(V4L2_MBUS_PCLK_SAMPLE_RISING | - V4L2_MBUS_PCLK_SAMPLE_FALLING); - flags |= v ? V4L2_MBUS_PCLK_SAMPLE_RISING : - V4L2_MBUS_PCLK_SAMPLE_FALLING; - pr_debug("pclk-sample %s\n", v ? "high" : "low"); + V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_PCLK_SAMPLE_DUALEDGE); + switch (v) { + case 0: + flags |= V4L2_MBUS_PCLK_SAMPLE_FALLING; + pr_debug("pclk-sample low\n"); + break; + case 1: + flags |= V4L2_MBUS_PCLK_SAMPLE_RISING; + pr_debug("pclk-sample high\n"); + break; + case 2: + flags |= V4L2_MBUS_PCLK_SAMPLE_DUALEDGE; + pr_debug("pclk-sample dual edge\n"); + break; + default: + pr_warn("invalid argument for pclk-sample"); + break; + } } if (!fwnode_property_read_u32(fwnode, "data-active", &v)) { diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index fddba75d9074..8e0a0ff62a70 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -31,12 +31,6 @@ #include <trace/events/v4l2.h> -/* Zero out the end of the struct pointed to by p. Everything after, but - * not including, the specified field is cleared. */ -#define CLEAR_AFTER_FIELD(p, field) \ - memset((u8 *)(p) + offsetof(typeof(*(p)), field) + sizeof((p)->field), \ - 0, sizeof(*(p)) - offsetof(typeof(*(p)), field) - sizeof((p)->field)) - #define is_valid_ioctl(vfd, cmd) test_bit(_IOC_NR(cmd), (vfd)->valid_ioctls) struct std_descr { @@ -1347,23 +1341,23 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_YUV420: descr = "Planar YUV 4:2:0"; break; case V4L2_PIX_FMT_HI240: descr = "8-bit Dithered RGB (BTTV)"; break; case V4L2_PIX_FMT_M420: descr = "YUV 4:2:0 (M420)"; break; - case V4L2_PIX_FMT_NV12: descr = "Y/CbCr 4:2:0"; break; - case V4L2_PIX_FMT_NV21: descr = "Y/CrCb 4:2:0"; break; - case V4L2_PIX_FMT_NV16: descr = "Y/CbCr 4:2:2"; break; - case V4L2_PIX_FMT_NV61: descr = "Y/CrCb 4:2:2"; break; - case V4L2_PIX_FMT_NV24: descr = "Y/CbCr 4:4:4"; break; - case V4L2_PIX_FMT_NV42: descr = "Y/CrCb 4:4:4"; break; - case V4L2_PIX_FMT_P010: descr = "10-bit Y/CbCr 4:2:0"; break; - case V4L2_PIX_FMT_NV12_4L4: descr = "Y/CbCr 4:2:0 (4x4 Linear)"; break; - case V4L2_PIX_FMT_NV12_16L16: descr = "Y/CbCr 4:2:0 (16x16 Linear)"; break; - case V4L2_PIX_FMT_NV12_32L32: descr = "Y/CbCr 4:2:0 (32x32 Linear)"; break; - case V4L2_PIX_FMT_P010_4L4: descr = "10-bit Y/CbCr 4:2:0 (4x4 Linear)"; break; - case V4L2_PIX_FMT_NV12M: descr = "Y/CbCr 4:2:0 (N-C)"; break; - case V4L2_PIX_FMT_NV21M: descr = "Y/CrCb 4:2:0 (N-C)"; break; - case V4L2_PIX_FMT_NV16M: descr = "Y/CbCr 4:2:2 (N-C)"; break; - case V4L2_PIX_FMT_NV61M: descr = "Y/CrCb 4:2:2 (N-C)"; break; - case V4L2_PIX_FMT_NV12MT: descr = "Y/CbCr 4:2:0 (64x32 MB, N-C)"; break; - case V4L2_PIX_FMT_NV12MT_16X16: descr = "Y/CbCr 4:2:0 (16x16 MB, N-C)"; break; + case V4L2_PIX_FMT_NV12: descr = "Y/UV 4:2:0"; break; + case V4L2_PIX_FMT_NV21: descr = "Y/VU 4:2:0"; break; + case V4L2_PIX_FMT_NV16: descr = "Y/UV 4:2:2"; break; + case V4L2_PIX_FMT_NV61: descr = "Y/VU 4:2:2"; break; + case V4L2_PIX_FMT_NV24: descr = "Y/UV 4:4:4"; break; + case V4L2_PIX_FMT_NV42: descr = "Y/VU 4:4:4"; break; + case V4L2_PIX_FMT_P010: descr = "10-bit Y/UV 4:2:0"; break; + case V4L2_PIX_FMT_NV12_4L4: descr = "Y/UV 4:2:0 (4x4 Linear)"; break; + case V4L2_PIX_FMT_NV12_16L16: descr = "Y/UV 4:2:0 (16x16 Linear)"; break; + case V4L2_PIX_FMT_NV12_32L32: descr = "Y/UV 4:2:0 (32x32 Linear)"; break; + case V4L2_PIX_FMT_P010_4L4: descr = "10-bit Y/UV 4:2:0 (4x4 Linear)"; break; + case V4L2_PIX_FMT_NV12M: descr = "Y/UV 4:2:0 (N-C)"; break; + case V4L2_PIX_FMT_NV21M: descr = "Y/VU 4:2:0 (N-C)"; break; + case V4L2_PIX_FMT_NV16M: descr = "Y/UV 4:2:2 (N-C)"; break; + case V4L2_PIX_FMT_NV61M: descr = "Y/VU 4:2:2 (N-C)"; break; + case V4L2_PIX_FMT_NV12MT: descr = "Y/UV 4:2:0 (64x32 MB, N-C)"; break; + case V4L2_PIX_FMT_NV12MT_16X16: descr = "Y/UV 4:2:0 (16x16 MB, N-C)"; break; case V4L2_PIX_FMT_YUV420M: descr = "Planar YUV 4:2:0 (N-C)"; break; case V4L2_PIX_FMT_YVU420M: descr = "Planar YVU 4:2:0 (N-C)"; break; case V4L2_PIX_FMT_YUV422M: descr = "Planar YUV 4:2:2 (N-C)"; break; @@ -1444,7 +1438,9 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_VIVID: descr = "Vivid Metadata"; break; case V4L2_META_FMT_RK_ISP1_PARAMS: descr = "Rockchip ISP1 3A Parameters"; break; case V4L2_META_FMT_RK_ISP1_STAT_3A: descr = "Rockchip ISP1 3A Statistics"; break; + case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; + case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; case V4L2_PIX_FMT_NV12M_10BE_8L128: descr = "10-bit NV12M (8x128 Linear, BE)"; break; default: @@ -1497,6 +1493,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_MT21C: descr = "Mediatek Compressed Format"; break; case V4L2_PIX_FMT_QC08C: descr = "QCOM Compressed 8-bit Format"; break; case V4L2_PIX_FMT_QC10C: descr = "QCOM Compressed 10-bit Format"; break; + case V4L2_PIX_FMT_AJPG: descr = "Aspeed JPEG"; break; default: if (fmt->description[0]) return; @@ -1530,7 +1527,7 @@ static int v4l_enum_fmt(const struct v4l2_ioctl_ops *ops, p->mbus_code = 0; mbus_code = p->mbus_code; - CLEAR_AFTER_FIELD(p, type); + memset_after(p, 0, type); p->mbus_code = mbus_code; switch (p->type) { @@ -1705,7 +1702,7 @@ static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_vid_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_s_fmt_vid_cap(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1715,30 +1712,30 @@ static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: if (unlikely(!ops->vidioc_s_fmt_vid_cap_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_s_fmt_vid_cap_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OVERLAY: if (unlikely(!ops->vidioc_s_fmt_vid_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_s_fmt_vid_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_s_fmt_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_s_fmt_sliced_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_vid_out)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_s_fmt_vid_out(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1746,45 +1743,45 @@ static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: if (unlikely(!ops->vidioc_s_fmt_vid_out_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_s_fmt_vid_out_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: if (unlikely(!ops->vidioc_s_fmt_vid_out_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_s_fmt_vid_out_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_s_fmt_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_s_fmt_sliced_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SDR_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_sdr_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_s_fmt_sdr_cap(file, fh, arg); case V4L2_BUF_TYPE_SDR_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_sdr_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_s_fmt_sdr_out(file, fh, arg); case V4L2_BUF_TYPE_META_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_meta_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_s_fmt_meta_cap(file, fh, arg); case V4L2_BUF_TYPE_META_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_meta_out)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_s_fmt_meta_out(file, fh, arg); } return -EINVAL; @@ -1807,7 +1804,7 @@ static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_vid_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_try_fmt_vid_cap(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1817,30 +1814,30 @@ static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: if (unlikely(!ops->vidioc_try_fmt_vid_cap_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_try_fmt_vid_cap_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OVERLAY: if (unlikely(!ops->vidioc_try_fmt_vid_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_try_fmt_vid_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_try_fmt_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_try_fmt_sliced_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_vid_out)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_try_fmt_vid_out(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1848,45 +1845,45 @@ static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: if (unlikely(!ops->vidioc_try_fmt_vid_out_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_try_fmt_vid_out_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: if (unlikely(!ops->vidioc_try_fmt_vid_out_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_try_fmt_vid_out_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_try_fmt_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_try_fmt_sliced_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SDR_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_sdr_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_try_fmt_sdr_cap(file, fh, arg); case V4L2_BUF_TYPE_SDR_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_sdr_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_try_fmt_sdr_out(file, fh, arg); case V4L2_BUF_TYPE_META_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_meta_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_try_fmt_meta_cap(file, fh, arg); case V4L2_BUF_TYPE_META_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_meta_out)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_try_fmt_meta_out(file, fh, arg); } return -EINVAL; @@ -2085,7 +2082,7 @@ static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops, if (ret) return ret; - CLEAR_AFTER_FIELD(p, flags); + memset_after(p, 0, flags); return ops->vidioc_reqbufs(file, fh, p); } @@ -2126,7 +2123,7 @@ static int v4l_create_bufs(const struct v4l2_ioctl_ops *ops, if (ret) return ret; - CLEAR_AFTER_FIELD(create, flags); + memset_after(create, 0, flags); v4l_sanitize_format(&create->format); diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 5c27bac772ea..4988a25bd8f4 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -318,6 +318,20 @@ static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, sd->ops->pad->get_mbus_config(sd, pad, config); } +static int call_s_stream(struct v4l2_subdev *sd, int enable) +{ + int ret; + + ret = sd->ops->video->s_stream(sd, enable); + + if (!enable && ret < 0) { + dev_warn(sd->dev, "disabling streaming failed (%d)\n", ret); + return 0; + } + + return ret; +} + #ifdef CONFIG_MEDIA_CONTROLLER /* * Create state-management wrapper for pad ops dealing with subdev state. The @@ -377,6 +391,7 @@ static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = { static const struct v4l2_subdev_video_ops v4l2_subdev_call_video_wrappers = { .g_frame_interval = call_g_frame_interval, .s_frame_interval = call_s_frame_interval, + .s_stream = call_s_stream, }; const struct v4l2_subdev_ops v4l2_subdev_call_wrappers = { @@ -845,7 +860,7 @@ int v4l2_subdev_get_fwnode_pad_1_to_1(struct media_entity *entity, fwnode = fwnode_graph_get_port_parent(endpoint->local_fwnode); fwnode_handle_put(fwnode); - if (dev_fwnode(sd->dev) == fwnode) + if (device_match_fwnode(sd->dev, fwnode)) return endpoint->port; return -ENXIO; diff --git a/drivers/phy/phy-core-mipi-dphy.c b/drivers/phy/phy-core-mipi-dphy.c index 929e86d6558e..f4956a417a47 100644 --- a/drivers/phy/phy-core-mipi-dphy.c +++ b/drivers/phy/phy-core-mipi-dphy.c @@ -17,19 +17,21 @@ * from the valid ranges specified in Section 6.9, Table 14, Page 41 * of the D-PHY specification (v1.2). */ -int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, +static int phy_mipi_dphy_calc_config(unsigned long pixel_clock, unsigned int bpp, unsigned int lanes, + unsigned long long hs_clk_rate, struct phy_configure_opts_mipi_dphy *cfg) { - unsigned long long hs_clk_rate; unsigned long long ui; if (!cfg) return -EINVAL; - hs_clk_rate = pixel_clock * bpp; - do_div(hs_clk_rate, lanes); + if (!hs_clk_rate) { + hs_clk_rate = pixel_clock * bpp; + do_div(hs_clk_rate, lanes); + } ui = ALIGN(PSEC_PER_SEC, hs_clk_rate); do_div(ui, hs_clk_rate); @@ -75,8 +77,29 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, return 0; } + +int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, + unsigned int bpp, + unsigned int lanes, + struct phy_configure_opts_mipi_dphy *cfg) +{ + return phy_mipi_dphy_calc_config(pixel_clock, bpp, lanes, 0, cfg); + +} EXPORT_SYMBOL(phy_mipi_dphy_get_default_config); +int phy_mipi_dphy_get_default_config_for_hsclk(unsigned long long hs_clk_rate, + unsigned int lanes, + struct phy_configure_opts_mipi_dphy *cfg) +{ + if (!hs_clk_rate) + return -EINVAL; + + return phy_mipi_dphy_calc_config(0, 0, lanes, hs_clk_rate, cfg); + +} +EXPORT_SYMBOL(phy_mipi_dphy_get_default_config_for_hsclk); + /* * Validate D-PHY configuration according to MIPI D-PHY specification * (v1.2, Section Section 6.9 "Global Operation Timing Parameters"). diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index d4f03b203ae5..b79f93684c4f 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -51,6 +51,7 @@ menuconfig STAGING_MEDIA_DEPRECATED If in doubt, say N here. if STAGING_MEDIA_DEPRECATED +source "drivers/staging/media/deprecated/atmel/Kconfig" source "drivers/staging/media/deprecated/cpia2/Kconfig" source "drivers/staging/media/deprecated/fsl-viu/Kconfig" source "drivers/staging/media/deprecated/meye/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index a387692b84f2..54bbdd4b0d08 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += deprecated/atmel/ obj-$(CONFIG_INTEL_ATOMISP) += atomisp/ obj-$(CONFIG_VIDEO_CPIA2) += deprecated/cpia2/ obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/ diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 783f1b88ebf2..87a634bf9ff5 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -786,8 +786,6 @@ static int gpio_ctrl(struct v4l2_subdev *sd, bool flag) return ret; } -static int power_down(struct v4l2_subdev *sd); - static int power_up(struct v4l2_subdev *sd) { struct gc0310_device *dev = to_gc0310_sensor(sd); @@ -800,6 +798,9 @@ static int power_up(struct v4l2_subdev *sd) return -ENODEV; } + if (dev->power_on) + return 0; /* Already on */ + /* power control */ ret = power_ctrl(sd, 1); if (ret) @@ -820,6 +821,7 @@ static int power_up(struct v4l2_subdev *sd) msleep(100); + dev->power_on = true; return 0; fail_gpio: @@ -844,6 +846,9 @@ static int power_down(struct v4l2_subdev *sd) return -ENODEV; } + if (!dev->power_on) + return 0; /* Already off */ + /* gpio ctrl */ ret = gpio_ctrl(sd, 0); if (ret) { @@ -861,6 +866,7 @@ static int power_down(struct v4l2_subdev *sd) if (ret) dev_err(&client->dev, "vprog failed.\n"); + dev->power_on = false; return ret; } @@ -935,6 +941,9 @@ static int gc0310_set_fmt(struct v4l2_subdev *sd, return 0; } + /* s_power has not been called yet for std v4l2 clients (camorama) */ + power_up(sd); + dev_dbg(&client->dev, "%s: before gc0310_write_reg_array %s\n", __func__, dev->res->desc); ret = startup(sd); @@ -1073,6 +1082,7 @@ static int gc0310_s_config(struct v4l2_subdev *sd, * as first power on by board may not fulfill the * power on sequqence needed by the module */ + dev->power_on = true; /* force power_down() to run */ ret = power_down(sd); if (ret) { dev_err(&client->dev, "gc0310 power-off err.\n"); diff --git a/drivers/staging/media/atomisp/i2c/gc0310.h b/drivers/staging/media/atomisp/i2c/gc0310.h index db643ebc3909..4b9ce681bd93 100644 --- a/drivers/staging/media/atomisp/i2c/gc0310.h +++ b/drivers/staging/media/atomisp/i2c/gc0310.h @@ -152,6 +152,7 @@ struct gc0310_device { int vt_pix_clk_freq_mhz; struct gc0310_resolution *res; u8 type; + bool power_on; }; enum gc0310_tok_type { diff --git a/drivers/staging/media/atomisp/i2c/ov2680.h b/drivers/staging/media/atomisp/i2c/ov2680.h index 4e351196fe34..7ab337b859ad 100644 --- a/drivers/staging/media/atomisp/i2c/ov2680.h +++ b/drivers/staging/media/atomisp/i2c/ov2680.h @@ -485,19 +485,19 @@ static struct ov2680_reg const ov2680_720x592_30fps[] = { static struct ov2680_reg const ov2680_800x600_30fps[] = { {0x3086, 0x01}, {0x370a, 0x23}, - {0x3801, 0x00}, + {0x3801, 0x00}, /* hstart 0 */ {0x3802, 0x00}, - {0x3803, 0x00}, + {0x3803, 0x00}, /* vstart 0 */ {0x3804, 0x06}, - {0x3805, 0x4f}, + {0x3805, 0x4f}, /* hend 1615 */ {0x3806, 0x04}, - {0x3807, 0xbf}, + {0x3807, 0xbf}, /* vend 1215 */ {0x3808, 0x03}, - {0x3809, 0x20}, + {0x3809, 0x20}, /* hsize 800 */ {0x380a, 0x02}, - {0x380b, 0x58}, + {0x380b, 0x58}, /* vsize 600 */ {0x380c, 0x06}, - {0x380d, 0xac}, + {0x380d, 0xac}, /* htotal 1708 */ {0x3810, 0x00}, {0x3811, 0x00}, {0x3812, 0x00}, @@ -524,19 +524,19 @@ static struct ov2680_reg const ov2680_800x600_30fps[] = { static struct ov2680_reg const ov2680_720p_30fps[] = { {0x3086, 0x00}, {0x370a, 0x21}, - {0x3801, 0xa0}, + {0x3801, 0xa0}, /* hstart 160 */ {0x3802, 0x00}, - {0x3803, 0xf2}, + {0x3803, 0xf2}, /* vstart 242 */ {0x3804, 0x05}, - {0x3805, 0xbf}, + {0x3805, 0xbf}, /* hend 1471 */ {0x3806, 0x03}, - {0x3807, 0xdd}, + {0x3807, 0xdd}, /* vend 989 */ {0x3808, 0x05}, - {0x3809, 0x10}, + {0x3809, 0x10}, /* hsize 1296 */ {0x380a, 0x02}, - {0x380b, 0xe0}, + {0x380b, 0xe0}, /* vsize 736 */ {0x380c, 0x06}, - {0x380d, 0xa8}, + {0x380d, 0xa8}, /* htotal 1704 */ {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, @@ -563,19 +563,19 @@ static struct ov2680_reg const ov2680_720p_30fps[] = { static struct ov2680_reg const ov2680_1296x976_30fps[] = { {0x3086, 0x00}, {0x370a, 0x21}, - {0x3801, 0xa0}, + {0x3801, 0xa0}, /* hstart 160 */ {0x3802, 0x00}, - {0x3803, 0x78}, + {0x3803, 0x78}, /* vstart 120 */ {0x3804, 0x05}, - {0x3805, 0xbf}, + {0x3805, 0xbf}, /* hend 1471 */ {0x3806, 0x04}, - {0x3807, 0x57}, + {0x3807, 0x57}, /* vend 1111 */ {0x3808, 0x05}, - {0x3809, 0x10}, + {0x3809, 0x10}, /* hsize 1296 */ {0x380a, 0x03}, - {0x380b, 0xd0}, + {0x380b, 0xd0}, /* vsize 976 */ {0x380c, 0x06}, - {0x380d, 0xa8}, + {0x380d, 0xa8}, /* htotal 1704 */ {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, @@ -820,8 +820,8 @@ static struct ov2680_resolution ov2680_res_preview[] = { .regs = ov2680_1296x976_30fps, }, { - .width = 1280, - .height = 720, + .width = 1296, + .height = 736, .fps = 60, .pix_clk_freq = 66, .pixels_per_line = 1698,//1704, diff --git a/drivers/staging/media/atomisp/include/hmm/hmm.h b/drivers/staging/media/atomisp/include/hmm/hmm.h index c0384bb0a762..2bc323b34f89 100644 --- a/drivers/staging/media/atomisp/include/hmm/hmm.h +++ b/drivers/staging/media/atomisp/include/hmm/hmm.h @@ -37,7 +37,8 @@ int hmm_init(void); void hmm_cleanup(void); ia_css_ptr hmm_alloc(size_t bytes); -ia_css_ptr hmm_create_from_userdata(size_t bytes, const void __user *userptr); +ia_css_ptr hmm_create_from_vmalloc_buf(size_t bytes, void *vmalloc_addr); + void hmm_free(ia_css_ptr ptr); int hmm_load(ia_css_ptr virt, void *data, unsigned int bytes); int hmm_store(ia_css_ptr virt, const void *data, unsigned int bytes); diff --git a/drivers/staging/media/atomisp/include/hmm/hmm_bo.h b/drivers/staging/media/atomisp/include/hmm/hmm_bo.h index c5cbae1d9cf9..b4c03e0ca9c0 100644 --- a/drivers/staging/media/atomisp/include/hmm/hmm_bo.h +++ b/drivers/staging/media/atomisp/include/hmm/hmm_bo.h @@ -73,7 +73,7 @@ enum hmm_bo_type { HMM_BO_PRIVATE, - HMM_BO_USER, + HMM_BO_VMALLOC, HMM_BO_LAST, }; @@ -207,7 +207,7 @@ int hmm_bo_allocated(struct hmm_buffer_object *bo); */ int hmm_bo_alloc_pages(struct hmm_buffer_object *bo, enum hmm_bo_type type, - const void __user *userptr); + void *vmalloc_addr); void hmm_bo_free_pages(struct hmm_buffer_object *bo); int hmm_bo_page_allocated(struct hmm_buffer_object *bo); diff --git a/drivers/staging/media/atomisp/pci/atomisp_cmd.c b/drivers/staging/media/atomisp/pci/atomisp_cmd.c index c72d0e344671..d8c7e7367386 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_cmd.c +++ b/drivers/staging/media/atomisp/pci/atomisp_cmd.c @@ -30,7 +30,6 @@ #include <asm/iosf_mbi.h> #include <media/v4l2-event.h> -#include <media/videobuf-vmalloc.h> #define CREATE_TRACE_POINTS #include "atomisp_trace_event.h" @@ -208,11 +207,6 @@ int atomisp_freq_scaling(struct atomisp_device *isp, int i, ret; unsigned short fps = 0; - if (isp->sw_contex.power_state != ATOM_ISP_POWER_UP) { - dev_err(isp->dev, "DFS cannot proceed due to no power.\n"); - return -EINVAL; - } - if ((pdev->device & ATOMISP_PCI_DEVICE_SOC_MASK) == ATOMISP_PCI_DEVICE_SOC_CHT && ATOMISP_USE_YUVPP(asd)) isp->dfs = &dfs_config_cht_soc; @@ -308,24 +302,16 @@ int atomisp_reset(struct atomisp_device *isp) int ret = 0; dev_dbg(isp->dev, "%s\n", __func__); - atomisp_css_suspend(isp); - ret = atomisp_runtime_suspend(isp->dev); + + ret = atomisp_power_off(isp->dev); if (ret < 0) - dev_err(isp->dev, "atomisp_runtime_suspend failed, %d\n", ret); - ret = atomisp_mrfld_power_down(isp); + dev_err(isp->dev, "atomisp_power_off failed, %d\n", ret); + + ret = atomisp_power_on(isp->dev); if (ret < 0) { - dev_err(isp->dev, "can not disable ISP power\n"); - } else { - ret = atomisp_mrfld_power_up(isp); - if (ret < 0) - dev_err(isp->dev, "can not enable ISP power\n"); - ret = atomisp_runtime_resume(isp->dev); - if (ret < 0) - dev_err(isp->dev, "atomisp_runtime_resume failed, %d\n", ret); - } - ret = atomisp_css_resume(isp); - if (ret) + dev_err(isp->dev, "atomisp_power_on failed, %d\n", ret); isp->isp_fatal_error = true; + } return ret; } @@ -518,8 +504,8 @@ irqreturn_t atomisp_isr(int irq, void *dev) int err; spin_lock_irqsave(&isp->lock, flags); - if (isp->sw_contex.power_state != ATOM_ISP_POWER_UP || - !isp->css_initialized) { + + if (!isp->css_initialized) { spin_unlock_irqrestore(&isp->lock, flags); return IRQ_HANDLED; } @@ -634,25 +620,6 @@ void atomisp_clear_css_buffer_counters(struct atomisp_sub_device *asd) memset(asd->metadata_bufs_in_css[i], 0, sizeof(asd->metadata_bufs_in_css[i])); asd->dis_bufs_in_css = 0; - asd->video_out_capture.buffers_in_css = 0; - asd->video_out_vf.buffers_in_css = 0; - asd->video_out_preview.buffers_in_css = 0; - asd->video_out_video_capture.buffers_in_css = 0; -} - -/* ISP2400 */ -bool atomisp_buffers_queued(struct atomisp_sub_device *asd) -{ - return asd->video_out_capture.buffers_in_css || - asd->video_out_vf.buffers_in_css || - asd->video_out_preview.buffers_in_css || - asd->video_out_video_capture.buffers_in_css; -} - -/* ISP2401 */ -bool atomisp_buffers_queued_pipe(struct atomisp_video_pipe *pipe) -{ - return pipe->buffers_in_css ? true : false; } /* 0x100000 is the start of dmem inside SP */ @@ -681,57 +648,68 @@ void dump_sp_dmem(struct atomisp_device *isp, unsigned int addr, } while (--size32); } -static struct videobuf_buffer *atomisp_css_frame_to_vbuf( - struct atomisp_video_pipe *pipe, struct ia_css_frame *frame) +int atomisp_buffers_in_css(struct atomisp_video_pipe *pipe) { - struct videobuf_vmalloc_memory *vm_mem; - struct ia_css_frame *handle; - int i; + unsigned long irqflags; + struct list_head *pos; + int buffers_in_css = 0; - for (i = 0; pipe->capq.bufs[i]; i++) { - vm_mem = pipe->capq.bufs[i]->priv; - handle = vm_mem->vaddr; - if (handle && handle->data == frame->data) - return pipe->capq.bufs[i]; - } + spin_lock_irqsave(&pipe->irq_lock, irqflags); - return NULL; + list_for_each(pos, &pipe->buffers_in_css) + buffers_in_css++; + + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + + return buffers_in_css; } -static void atomisp_flush_video_pipe(struct atomisp_sub_device *asd, - struct atomisp_video_pipe *pipe) +void atomisp_buffer_done(struct ia_css_frame *frame, enum vb2_buffer_state state) { + struct atomisp_video_pipe *pipe = vb_to_pipe(&frame->vb.vb2_buf); + + lockdep_assert_held(&pipe->irq_lock); + + frame->vb.vb2_buf.timestamp = ktime_get_ns(); + frame->vb.field = pipe->pix.field; + frame->vb.sequence = atomic_read(&pipe->asd->sequence); + list_del(&frame->queue); + if (state == VB2_BUF_STATE_DONE) + vb2_set_plane_payload(&frame->vb.vb2_buf, 0, pipe->pix.sizeimage); + vb2_buffer_done(&frame->vb.vb2_buf, state); +} + +void atomisp_flush_video_pipe(struct atomisp_video_pipe *pipe, bool warn_on_css_frames) +{ + struct ia_css_frame *frame, *_frame; unsigned long irqflags; - int i; - if (!pipe->users) - return; + spin_lock_irqsave(&pipe->irq_lock, irqflags); - for (i = 0; pipe->capq.bufs[i]; i++) { - spin_lock_irqsave(&pipe->irq_lock, irqflags); - if (pipe->capq.bufs[i]->state == VIDEOBUF_ACTIVE || - pipe->capq.bufs[i]->state == VIDEOBUF_QUEUED) { - pipe->capq.bufs[i]->ts = ktime_get_ns(); - pipe->capq.bufs[i]->field_count = - atomic_read(&asd->sequence) << 1; - dev_dbg(asd->isp->dev, "release buffers on device %s\n", - pipe->vdev.name); - if (pipe->capq.bufs[i]->state == VIDEOBUF_QUEUED) - list_del_init(&pipe->capq.bufs[i]->queue); - pipe->capq.bufs[i]->state = VIDEOBUF_ERROR; - wake_up(&pipe->capq.bufs[i]->done); - } - spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + list_for_each_entry_safe(frame, _frame, &pipe->buffers_in_css, queue) { + if (warn_on_css_frames) + dev_warn(pipe->isp->dev, "Warning: CSS frames queued on flush\n"); + atomisp_buffer_done(frame, VB2_BUF_STATE_ERROR); + } + + list_for_each_entry_safe(frame, _frame, &pipe->activeq, queue) + atomisp_buffer_done(frame, VB2_BUF_STATE_ERROR); + + list_for_each_entry_safe(frame, _frame, &pipe->buffers_waiting_for_param, queue) { + pipe->frame_request_config_id[frame->vb.vb2_buf.index] = 0; + atomisp_buffer_done(frame, VB2_BUF_STATE_ERROR); } + + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); } /* Returns queued buffers back to video-core */ void atomisp_flush_bufs_and_wakeup(struct atomisp_sub_device *asd) { - atomisp_flush_video_pipe(asd, &asd->video_out_capture); - atomisp_flush_video_pipe(asd, &asd->video_out_vf); - atomisp_flush_video_pipe(asd, &asd->video_out_preview); - atomisp_flush_video_pipe(asd, &asd->video_out_video_capture); + atomisp_flush_video_pipe(&asd->video_out_capture, false); + atomisp_flush_video_pipe(&asd->video_out_vf, false); + atomisp_flush_video_pipe(&asd->video_out_preview, false); + atomisp_flush_video_pipe(&asd->video_out_video_capture, false); } /* clean out the parameters that did not apply */ @@ -763,93 +741,6 @@ static void atomisp_recover_params_queue(struct atomisp_video_pipe *pipe) atomisp_handle_parameter_and_buffer(pipe); } -/* find atomisp_video_pipe with css pipe id, buffer type and atomisp run_mode */ -static struct atomisp_video_pipe *__atomisp_get_pipe( - struct atomisp_sub_device *asd, - enum atomisp_input_stream_id stream_id, - enum ia_css_pipe_id css_pipe_id, - enum ia_css_buffer_type buf_type) -{ - /* video is same in online as in continuouscapture mode */ - if (asd->vfpp->val == ATOMISP_VFPP_DISABLE_LOWLAT) { - /* - * Disable vf_pp and run CSS in still capture mode. In this - * mode, CSS does not cause extra latency with buffering, but - * scaling is not available. - */ - return &asd->video_out_capture; - } else if (asd->vfpp->val == ATOMISP_VFPP_DISABLE_SCALER) { - /* - * Disable vf_pp and run CSS in video mode. This allows using - * ISP scaling but it has one frame delay due to CSS internal - * buffering. - */ - return &asd->video_out_video_capture; - } else if (css_pipe_id == IA_CSS_PIPE_ID_YUVPP) { - /* - * to SOC camera, yuvpp pipe is run for capture/video/SDV/ZSL. - */ - if (asd->continuous_mode->val) { - if (asd->run_mode->val == ATOMISP_RUN_MODE_VIDEO) { - /* SDV case */ - switch (buf_type) { - case IA_CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME: - return &asd->video_out_video_capture; - case IA_CSS_BUFFER_TYPE_SEC_VF_OUTPUT_FRAME: - return &asd->video_out_preview; - case IA_CSS_BUFFER_TYPE_OUTPUT_FRAME: - return &asd->video_out_capture; - default: - return &asd->video_out_vf; - } - } else if (asd->run_mode->val == ATOMISP_RUN_MODE_PREVIEW) { - /* ZSL case */ - switch (buf_type) { - case IA_CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME: - return &asd->video_out_preview; - case IA_CSS_BUFFER_TYPE_OUTPUT_FRAME: - return &asd->video_out_capture; - default: - return &asd->video_out_vf; - } - } - } else if (buf_type == IA_CSS_BUFFER_TYPE_OUTPUT_FRAME) { - switch (asd->run_mode->val) { - case ATOMISP_RUN_MODE_VIDEO: - return &asd->video_out_video_capture; - case ATOMISP_RUN_MODE_PREVIEW: - return &asd->video_out_preview; - default: - return &asd->video_out_capture; - } - } else if (buf_type == IA_CSS_BUFFER_TYPE_VF_OUTPUT_FRAME) { - if (asd->run_mode->val == ATOMISP_RUN_MODE_VIDEO) - return &asd->video_out_preview; - else - return &asd->video_out_vf; - } - } else if (asd->run_mode->val == ATOMISP_RUN_MODE_VIDEO) { - /* For online video or SDV video pipe. */ - if (css_pipe_id == IA_CSS_PIPE_ID_VIDEO || - css_pipe_id == IA_CSS_PIPE_ID_COPY || - css_pipe_id == IA_CSS_PIPE_ID_YUVPP) { - if (buf_type == IA_CSS_BUFFER_TYPE_OUTPUT_FRAME) - return &asd->video_out_video_capture; - return &asd->video_out_preview; - } - } else if (asd->run_mode->val == ATOMISP_RUN_MODE_PREVIEW) { - /* For online preview or ZSL preview pipe. */ - if (css_pipe_id == IA_CSS_PIPE_ID_PREVIEW || - css_pipe_id == IA_CSS_PIPE_ID_COPY || - css_pipe_id == IA_CSS_PIPE_ID_YUVPP) - return &asd->video_out_preview; - } - /* For capture pipe. */ - if (buf_type == IA_CSS_BUFFER_TYPE_VF_OUTPUT_FRAME) - return &asd->video_out_vf; - return &asd->video_out_capture; -} - enum atomisp_metadata_type atomisp_get_metadata_type(struct atomisp_sub_device *asd, enum ia_css_pipe_id pipe_id) @@ -868,11 +759,9 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, enum ia_css_pipe_id css_pipe_id, bool q_buffers, enum atomisp_input_stream_id stream_id) { - struct videobuf_buffer *vb = NULL; struct atomisp_video_pipe *pipe = NULL; struct atomisp_css_buffer buffer; bool requeue = false; - int err; unsigned long irqflags; struct ia_css_frame *frame = NULL; struct atomisp_s3a_buf *s3a_buf = NULL, *_s3a_buf_tmp, *s3a_iter; @@ -881,6 +770,7 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, enum atomisp_metadata_type md_type; struct atomisp_device *isp = asd->isp; struct v4l2_control ctrl; + int i, err; lockdep_assert_held(&isp->mutex); @@ -908,13 +798,6 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, return; } - /* need to know the atomisp pipe for frame buffers */ - pipe = __atomisp_get_pipe(asd, stream_id, css_pipe_id, buf_type); - if (!pipe) { - dev_err(isp->dev, "error getting atomisp pipe\n"); - return; - } - switch (buf_type) { case IA_CSS_BUFFER_TYPE_3A_STATISTICS: list_for_each_entry_safe(s3a_iter, _s3a_buf_tmp, @@ -989,7 +872,6 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, break; case IA_CSS_BUFFER_TYPE_VF_OUTPUT_FRAME: case IA_CSS_BUFFER_TYPE_SEC_VF_OUTPUT_FRAME: - pipe->buffers_in_css--; frame = buffer.css_buffer.data.frame; if (!frame) { WARN_ON(1); @@ -998,6 +880,8 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, if (!frame->valid) error = true; + pipe = vb_to_pipe(&frame->vb.vb2_buf); + /* FIXME: * YUVPP doesn't set postview exp_id correctlly in SDV mode. * This is a WORKAROUND to set exp_id. see HSDES-1503911606. @@ -1022,10 +906,7 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, dev_dbg(isp->dev, "%s thumb no flash in this frame\n", __func__); } - vb = atomisp_css_frame_to_vbuf(pipe, frame); - WARN_ON(!vb); - if (vb) - pipe->frame_config_id[vb->i] = frame->isp_config_id; + pipe->frame_config_id[frame->vb.vb2_buf.index] = frame->isp_config_id; if (css_pipe_id == IA_CSS_PIPE_ID_CAPTURE && asd->pending_capture_request > 0) { err = atomisp_css_offline_capture_configure(asd, @@ -1041,7 +922,6 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, break; case IA_CSS_BUFFER_TYPE_OUTPUT_FRAME: case IA_CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME: - pipe->buffers_in_css--; frame = buffer.css_buffer.data.frame; if (!frame) { WARN_ON(1); @@ -1051,6 +931,8 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, if (!frame->valid) error = true; + pipe = vb_to_pipe(&frame->vb.vb2_buf); + /* FIXME: * YUVPP doesn't set preview exp_id correctlly in ZSL mode. * This is a WORKAROUND to set exp_id. see HSDES-1503911606. @@ -1062,72 +944,53 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, dev_dbg(isp->dev, "%s: main frame with exp_id %d is ready\n", __func__, frame->exp_id); - vb = atomisp_css_frame_to_vbuf(pipe, frame); - if (!vb) { - WARN_ON(1); - break; - } + + i = frame->vb.vb2_buf.index; /* free the parameters */ - if (pipe->frame_params[vb->i]) { - if (asd->params.dvs_6axis == - pipe->frame_params[vb->i]->params.dvs_6axis) + if (pipe->frame_params[i]) { + if (asd->params.dvs_6axis == pipe->frame_params[i]->params.dvs_6axis) asd->params.dvs_6axis = NULL; - atomisp_free_css_parameters( - &pipe->frame_params[vb->i]->params); - kvfree(pipe->frame_params[vb->i]); - pipe->frame_params[vb->i] = NULL; + atomisp_free_css_parameters(&pipe->frame_params[i]->params); + kvfree(pipe->frame_params[i]); + pipe->frame_params[i] = NULL; } - pipe->frame_config_id[vb->i] = frame->isp_config_id; + pipe->frame_config_id[i] = frame->isp_config_id; ctrl.id = V4L2_CID_FLASH_MODE; if (asd->params.flash_state == ATOMISP_FLASH_ONGOING) { - if (frame->flash_state - == IA_CSS_FRAME_FLASH_STATE_PARTIAL) { - asd->frame_status[vb->i] = - ATOMISP_FRAME_STATUS_FLASH_PARTIAL; - dev_dbg(isp->dev, "%s partially flashed\n", - __func__); - } else if (frame->flash_state - == IA_CSS_FRAME_FLASH_STATE_FULL) { - asd->frame_status[vb->i] = - ATOMISP_FRAME_STATUS_FLASH_EXPOSED; + if (frame->flash_state == IA_CSS_FRAME_FLASH_STATE_PARTIAL) { + asd->frame_status[i] = ATOMISP_FRAME_STATUS_FLASH_PARTIAL; + dev_dbg(isp->dev, "%s partially flashed\n", __func__); + } else if (frame->flash_state == IA_CSS_FRAME_FLASH_STATE_FULL) { + asd->frame_status[i] = ATOMISP_FRAME_STATUS_FLASH_EXPOSED; asd->params.num_flash_frames--; - dev_dbg(isp->dev, "%s completely flashed\n", - __func__); + dev_dbg(isp->dev, "%s completely flashed\n", __func__); } else { - asd->frame_status[vb->i] = - ATOMISP_FRAME_STATUS_OK; - dev_dbg(isp->dev, - "%s no flash in this frame\n", - __func__); + asd->frame_status[i] = ATOMISP_FRAME_STATUS_OK; + dev_dbg(isp->dev, "%s no flash in this frame\n", __func__); } /* Check if flashing sequence is done */ - if (asd->frame_status[vb->i] == - ATOMISP_FRAME_STATUS_FLASH_EXPOSED) + if (asd->frame_status[i] == ATOMISP_FRAME_STATUS_FLASH_EXPOSED) asd->params.flash_state = ATOMISP_FLASH_DONE; } else if (isp->flash) { - if (v4l2_g_ctrl(isp->flash->ctrl_handler, &ctrl) == - 0 && ctrl.value == ATOMISP_FLASH_MODE_TORCH) { + if (v4l2_g_ctrl(isp->flash->ctrl_handler, &ctrl) == 0 && + ctrl.value == ATOMISP_FLASH_MODE_TORCH) { ctrl.id = V4L2_CID_FLASH_TORCH_INTENSITY; - if (v4l2_g_ctrl(isp->flash->ctrl_handler, &ctrl) - == 0 && ctrl.value > 0) { - asd->frame_status[vb->i] = - ATOMISP_FRAME_STATUS_FLASH_EXPOSED; - } else { - asd->frame_status[vb->i] = - ATOMISP_FRAME_STATUS_OK; - } + if (v4l2_g_ctrl(isp->flash->ctrl_handler, &ctrl) == 0 && + ctrl.value > 0) + asd->frame_status[i] = ATOMISP_FRAME_STATUS_FLASH_EXPOSED; + else + asd->frame_status[i] = ATOMISP_FRAME_STATUS_OK; } else { - asd->frame_status[vb->i] = - ATOMISP_FRAME_STATUS_OK; + asd->frame_status[i] = ATOMISP_FRAME_STATUS_OK; } } else { - asd->frame_status[vb->i] = ATOMISP_FRAME_STATUS_OK; + asd->frame_status[i] = ATOMISP_FRAME_STATUS_OK; } - asd->params.last_frame_status = asd->frame_status[vb->i]; + asd->params.last_frame_status = asd->frame_status[i]; if (asd->continuous_mode->val) { if (css_pipe_id == IA_CSS_PIPE_ID_PREVIEW || @@ -1193,20 +1056,10 @@ void atomisp_buf_done(struct atomisp_sub_device *asd, int error, default: break; } - if (vb) { - vb->ts = ktime_get_ns(); - vb->field_count = atomic_read(&asd->sequence) << 1; - /*mark videobuffer done for dequeue*/ + if (frame) { spin_lock_irqsave(&pipe->irq_lock, irqflags); - vb->state = !error ? VIDEOBUF_DONE : VIDEOBUF_ERROR; + atomisp_buffer_done(frame, error ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); spin_unlock_irqrestore(&pipe->irq_lock, irqflags); - - /* - * Frame capture done, wake up any process block on - * current active buffer - * possibly hold by videobuf_dqbuf() - */ - wake_up(&vb->done); } /* @@ -3288,7 +3141,7 @@ int atomisp_css_cp_dvs2_coefs(struct atomisp_sub_device *asd, if (!IS_ISP2401) { if (sizeof(*cur) != sizeof(coefs->grid) || memcmp(&coefs->grid, cur, sizeof(coefs->grid))) { - dev_err(asd->isp->dev, "dvs grid mis-match!\n"); + dev_err(asd->isp->dev, "dvs grid mismatch!\n"); /* If the grid info in the argument differs from the current grid info, we tell the caller to reset the grid size and try again. */ @@ -3344,7 +3197,7 @@ int atomisp_css_cp_dvs2_coefs(struct atomisp_sub_device *asd, if (sizeof(*cur) != sizeof(dvs2_coefs.grid) || memcmp(&dvs2_coefs.grid, cur, sizeof(dvs2_coefs.grid))) { - dev_err(asd->isp->dev, "dvs grid mis-match!\n"); + dev_err(asd->isp->dev, "dvs grid mismatch!\n"); /* If the grid info in the argument differs from the current grid info, we tell the caller to reset the grid size and try again. */ @@ -3676,6 +3529,18 @@ void atomisp_free_css_parameters(struct atomisp_css_params *css_param) } } +static void atomisp_move_frame_to_activeq(struct ia_css_frame *frame, + struct atomisp_css_params_with_list *param) +{ + struct atomisp_video_pipe *pipe = vb_to_pipe(&frame->vb.vb2_buf); + unsigned long irqflags; + + pipe->frame_params[frame->vb.vb2_buf.index] = param; + spin_lock_irqsave(&pipe->irq_lock, irqflags); + list_move_tail(&frame->queue, &pipe->activeq); + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); +} + /* * Check parameter queue list and buffer queue list to find out if matched items * and then set parameter to CSS and enqueue buffer to CSS. @@ -3686,13 +3551,10 @@ void atomisp_free_css_parameters(struct atomisp_css_params *css_param) void atomisp_handle_parameter_and_buffer(struct atomisp_video_pipe *pipe) { struct atomisp_sub_device *asd = pipe->asd; - struct videobuf_buffer *vb = NULL, *vb_tmp; + struct ia_css_frame *frame = NULL, *frame_tmp; struct atomisp_css_params_with_list *param = NULL, *param_tmp; - struct videobuf_vmalloc_memory *vm_mem = NULL; - unsigned long irqflags; bool need_to_enqueue_buffer = false; - - lockdep_assert_held(&asd->isp->mutex); + int i; if (!asd) { dev_err(pipe->isp->dev, "%s(): asd is NULL, device is %s\n", @@ -3700,6 +3562,8 @@ void atomisp_handle_parameter_and_buffer(struct atomisp_video_pipe *pipe) return; } + lockdep_assert_held(&asd->isp->mutex); + if (atomisp_is_vf_pipe(pipe)) return; @@ -3714,44 +3578,32 @@ void atomisp_handle_parameter_and_buffer(struct atomisp_video_pipe *pipe) list_empty(&pipe->buffers_waiting_for_param)) return; - list_for_each_entry_safe(vb, vb_tmp, + list_for_each_entry_safe(frame, frame_tmp, &pipe->buffers_waiting_for_param, queue) { - if (pipe->frame_request_config_id[vb->i]) { + i = frame->vb.vb2_buf.index; + if (pipe->frame_request_config_id[i]) { list_for_each_entry_safe(param, param_tmp, &pipe->per_frame_params, list) { - if (pipe->frame_request_config_id[vb->i] != - param->params.isp_config_id) + if (pipe->frame_request_config_id[i] != param->params.isp_config_id) continue; list_del(¶m->list); - list_del(&vb->queue); + /* * clear the request config id as the buffer * will be handled and enqueued into CSS soon */ - pipe->frame_request_config_id[vb->i] = 0; - pipe->frame_params[vb->i] = param; - vm_mem = vb->priv; - BUG_ON(!vm_mem); + pipe->frame_request_config_id[i] = 0; + atomisp_move_frame_to_activeq(frame, param); + need_to_enqueue_buffer = true; break; } - if (vm_mem) { - spin_lock_irqsave(&pipe->irq_lock, irqflags); - list_add_tail(&vb->queue, &pipe->activeq); - spin_unlock_irqrestore(&pipe->irq_lock, irqflags); - vm_mem = NULL; - need_to_enqueue_buffer = true; - } else { - /* The is the end, stop further loop */ + /* If this is the end, stop further loop */ + if (list_entry_is_head(param, &pipe->per_frame_params, list)) break; - } } else { - list_del(&vb->queue); - pipe->frame_params[vb->i] = NULL; - spin_lock_irqsave(&pipe->irq_lock, irqflags); - list_add_tail(&vb->queue, &pipe->activeq); - spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + atomisp_move_frame_to_activeq(frame, NULL); need_to_enqueue_buffer = true; } } @@ -3774,14 +3626,14 @@ int atomisp_set_parameters(struct video_device *vdev, struct atomisp_css_params *css_param = &asd->params.css_param; int ret; - lockdep_assert_held(&asd->isp->mutex); - if (!asd) { dev_err(pipe->isp->dev, "%s(): asd is NULL, device is %s\n", __func__, vdev->name); return -EINVAL; } + lockdep_assert_held(&asd->isp->mutex); + if (!asd->stream_env[ATOMISP_INPUT_STREAM_GENERAL].stream) { dev_err(asd->isp->dev, "%s: internal error!\n", __func__); return -EINVAL; @@ -5140,9 +4992,8 @@ static int atomisp_set_fmt_to_snr(struct video_device *vdev, return css_input_resolution_changed(asd, ffmt); } -int atomisp_set_fmt(struct file *file, void *unused, struct v4l2_format *f) +int atomisp_set_fmt(struct video_device *vdev, struct v4l2_format *f) { - struct video_device *vdev = video_devdata(file); struct atomisp_device *isp = video_get_drvdata(vdev); struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); struct atomisp_sub_device *asd = pipe->asd; @@ -5558,8 +5409,6 @@ done: f->fmt.pix.priv = PAGE_ALIGN(pipe->pix.width * pipe->pix.height * 2); - pipe->capq.field = f->fmt.pix.field; - /* * If in video 480P case, no GFX throttle */ @@ -5643,51 +5492,6 @@ out: return ret; } -/*Turn off ISP dphy */ -int atomisp_ospm_dphy_down(struct atomisp_device *isp) -{ - struct pci_dev *pdev = to_pci_dev(isp->dev); - unsigned long flags; - u32 reg; - - dev_dbg(isp->dev, "%s\n", __func__); - - /* if ISP timeout, we can force powerdown */ - if (isp->isp_timeout) - goto done; - - if (!atomisp_dev_users(isp)) - goto done; - - spin_lock_irqsave(&isp->lock, flags); - isp->sw_contex.power_state = ATOM_ISP_POWER_DOWN; - spin_unlock_irqrestore(&isp->lock, flags); -done: - /* - * MRFLD IUNIT DPHY is located in an always-power-on island - * MRFLD HW design need all CSI ports are disabled before - * powering down the IUNIT. - */ - pci_read_config_dword(pdev, MRFLD_PCI_CSI_CONTROL, ®); - reg |= MRFLD_ALL_CSI_PORTS_OFF_MASK; - pci_write_config_dword(pdev, MRFLD_PCI_CSI_CONTROL, reg); - return 0; -} - -/*Turn on ISP dphy */ -int atomisp_ospm_dphy_up(struct atomisp_device *isp) -{ - unsigned long flags; - - dev_dbg(isp->dev, "%s\n", __func__); - - spin_lock_irqsave(&isp->lock, flags); - isp->sw_contex.power_state = ATOM_ISP_POWER_UP; - spin_unlock_irqrestore(&isp->lock, flags); - - return 0; -} - int atomisp_exif_makernote(struct atomisp_sub_device *asd, struct atomisp_makernote_info *config) { diff --git a/drivers/staging/media/atomisp/pci/atomisp_cmd.h b/drivers/staging/media/atomisp/pci/atomisp_cmd.h index c9f92f1326b6..b8911491581a 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_cmd.h +++ b/drivers/staging/media/atomisp/pci/atomisp_cmd.h @@ -55,12 +55,11 @@ void dump_sp_dmem(struct atomisp_device *isp, unsigned int addr, struct camera_mipi_info *atomisp_to_sensor_mipi_info(struct v4l2_subdev *sd); struct atomisp_video_pipe *atomisp_to_video_pipe(struct video_device *dev); int atomisp_reset(struct atomisp_device *isp); +int atomisp_buffers_in_css(struct atomisp_video_pipe *pipe); +void atomisp_buffer_done(struct ia_css_frame *frame, enum vb2_buffer_state state); +void atomisp_flush_video_pipe(struct atomisp_video_pipe *pipe, bool warn_on_css_frames); void atomisp_flush_bufs_and_wakeup(struct atomisp_sub_device *asd); void atomisp_clear_css_buffer_counters(struct atomisp_sub_device *asd); -/* ISP2400 */ -bool atomisp_buffers_queued(struct atomisp_sub_device *asd); -/* ISP2401 */ -bool atomisp_buffers_queued_pipe(struct atomisp_video_pipe *pipe); /* Interrupt functions */ void atomisp_msi_irq_init(struct atomisp_device *isp); @@ -266,7 +265,7 @@ int atomisp_get_sensor_mode_data(struct atomisp_sub_device *asd, int atomisp_try_fmt(struct video_device *vdev, struct v4l2_pix_format *f, bool *res_overflow); -int atomisp_set_fmt(struct file *file, void *fh, struct v4l2_format *f); +int atomisp_set_fmt(struct video_device *vdev, struct v4l2_format *f); int atomisp_set_shading_table(struct atomisp_sub_device *asd, struct atomisp_shading_table *shading_table); @@ -274,8 +273,6 @@ int atomisp_set_shading_table(struct atomisp_sub_device *asd, int atomisp_offline_capture_configure(struct atomisp_sub_device *asd, struct atomisp_cont_capture_conf *cvf_config); -int atomisp_ospm_dphy_down(struct atomisp_device *isp); -int atomisp_ospm_dphy_up(struct atomisp_device *isp); int atomisp_exif_makernote(struct atomisp_sub_device *asd, struct atomisp_makernote_info *config); @@ -342,8 +339,6 @@ int atomisp_inject_a_fake_event(struct atomisp_sub_device *asd, int *event); int atomisp_get_invalid_frame_num(struct video_device *vdev, int *invalid_frame_num); -int atomisp_mrfld_power_up(struct atomisp_device *isp); -int atomisp_mrfld_power_down(struct atomisp_device *isp); -int atomisp_runtime_suspend(struct device *dev); -int atomisp_runtime_resume(struct device *dev); +int atomisp_power_off(struct device *dev); +int atomisp_power_on(struct device *dev); #endif /* __ATOMISP_CMD_H__ */ diff --git a/drivers/staging/media/atomisp/pci/atomisp_common.h b/drivers/staging/media/atomisp/pci/atomisp_common.h index b29874f2bc0f..07c38e487a66 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_common.h +++ b/drivers/staging/media/atomisp/pci/atomisp_common.h @@ -25,7 +25,7 @@ #include <linux/v4l2-mediabus.h> -#include <media/videobuf-core.h> +#include <media/videobuf2-v4l2.h> #include "atomisp_compat.h" @@ -64,8 +64,4 @@ struct atomisp_fmt { u32 bayer_order; }; -struct atomisp_buffer { - struct videobuf_buffer vb; -}; - #endif diff --git a/drivers/staging/media/atomisp/pci/atomisp_compat.h b/drivers/staging/media/atomisp/pci/atomisp_compat.h index a6d85d0f9ae5..7316eb9f974a 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_compat.h +++ b/drivers/staging/media/atomisp/pci/atomisp_compat.h @@ -22,7 +22,6 @@ #include "atomisp_compat_css20.h" #include "../../include/linux/atomisp.h" -#include <media/videobuf-vmalloc.h> struct atomisp_device; struct atomisp_sub_device; @@ -42,10 +41,6 @@ int atomisp_css_init(struct atomisp_device *isp); void atomisp_css_uninit(struct atomisp_device *isp); -void atomisp_css_suspend(struct atomisp_device *isp); - -int atomisp_css_resume(struct atomisp_device *isp); - void atomisp_css_init_struct(struct atomisp_sub_device *asd); int atomisp_css_irq_translate(struct atomisp_device *isp, @@ -61,7 +56,7 @@ int atomisp_css_irq_enable(struct atomisp_device *isp, enum ia_css_irq_info info, bool enable); int atomisp_q_video_buffer_to_css(struct atomisp_sub_device *asd, - struct videobuf_vmalloc_memory *vm_mem, + struct ia_css_frame *frame, enum atomisp_input_stream_id stream_id, enum ia_css_buffer_type css_buf_type, enum ia_css_pipe_id css_pipe_id); @@ -258,13 +253,6 @@ int atomisp_css_yuvpp_configure_output(struct atomisp_sub_device *asd, unsigned int padded_width, enum ia_css_frame_format format); -int atomisp_css_yuvpp_configure_viewfinder( - struct atomisp_sub_device *asd, - unsigned int stream_index, - unsigned int width, unsigned int height, - unsigned int min_width, - enum ia_css_frame_format format); - int atomisp_css_yuvpp_get_output_frame_info( struct atomisp_sub_device *asd, unsigned int stream_index, diff --git a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c index fdc05548d972..61e2e63a0ef1 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c +++ b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c @@ -237,18 +237,6 @@ static void __dump_pipe_config(struct atomisp_sub_device *asd, "pipe_config.isp_pipe_version:%d.\n", p_config->isp_pipe_version); dev_dbg(isp->dev, - "pipe_config.acc_extension=%p.\n", - p_config->acc_extension); - dev_dbg(isp->dev, - "pipe_config.acc_stages=%p.\n", - p_config->acc_stages); - dev_dbg(isp->dev, - "pipe_config.num_acc_stages=%d.\n", - p_config->num_acc_stages); - dev_dbg(isp->dev, - "pipe_config.acc_num_execs=%d.\n", - p_config->acc_num_execs); - dev_dbg(isp->dev, "pipe_config.default_capture_config.capture_mode=%d.\n", p_config->default_capture_config.mode); dev_dbg(isp->dev, @@ -629,10 +617,6 @@ static void __apply_additional_pipe_config( else stream_env->pipe_configs[pipe_id].enable_dz = false; break; - case IA_CSS_PIPE_ID_ACC: - stream_env->pipe_configs[pipe_id].mode = IA_CSS_PIPE_MODE_ACC; - stream_env->pipe_configs[pipe_id].enable_dz = false; - break; default: break; } @@ -644,7 +628,7 @@ static bool is_pipe_valid_to_current_run_mode(struct atomisp_sub_device *asd, if (!asd) return false; - if (pipe_id == IA_CSS_PIPE_ID_ACC || pipe_id == IA_CSS_PIPE_ID_YUVPP) + if (pipe_id == IA_CSS_PIPE_ID_YUVPP) return true; if (asd->vfpp) { @@ -718,12 +702,7 @@ static int __create_pipe(struct atomisp_sub_device *asd, if (pipe_id >= IA_CSS_PIPE_ID_NUM) return -EINVAL; - if (pipe_id != IA_CSS_PIPE_ID_ACC && - !stream_env->pipe_configs[pipe_id].output_info[0].res.width) - return 0; - - if (pipe_id == IA_CSS_PIPE_ID_ACC && - !stream_env->pipe_configs[pipe_id].acc_extension) + if (!stream_env->pipe_configs[pipe_id].output_info[0].res.width) return 0; if (!is_pipe_valid_to_current_run_mode(asd, pipe_id)) @@ -885,48 +864,10 @@ int atomisp_css_load_firmware(struct atomisp_device *isp) void atomisp_css_uninit(struct atomisp_device *isp) { - struct atomisp_sub_device *asd; - unsigned int i; - - for (i = 0; i < isp->num_of_streams; i++) { - asd = &isp->asd[i]; - memset(&asd->params.config, 0, sizeof(asd->params.config)); - asd->params.css_update_params_needed = false; - } - - isp->css_initialized = false; - ia_css_uninit(); -} - -void atomisp_css_suspend(struct atomisp_device *isp) -{ isp->css_initialized = false; ia_css_uninit(); } -int atomisp_css_resume(struct atomisp_device *isp) -{ - unsigned int mmu_base_addr; - int ret; - - ret = hmm_get_mmu_base_addr(isp->dev, &mmu_base_addr); - if (ret) { - dev_err(isp->dev, "get base address error.\n"); - return -EINVAL; - } - - ret = ia_css_init(isp->dev, &isp->css_env.isp_css_env, NULL, - mmu_base_addr, IA_CSS_IRQ_TYPE_PULSE); - if (ret) { - dev_err(isp->dev, "re-init css failed.\n"); - return -EINVAL; - } - ia_css_enable_isys_event_queue(true); - - isp->css_initialized = true; - return 0; -} - int atomisp_css_irq_translate(struct atomisp_device *isp, unsigned int *infos) { @@ -996,7 +937,7 @@ void atomisp_css_init_struct(struct atomisp_sub_device *asd) } int atomisp_q_video_buffer_to_css(struct atomisp_sub_device *asd, - struct videobuf_vmalloc_memory *vm_mem, + struct ia_css_frame *frame, enum atomisp_input_stream_id stream_id, enum ia_css_buffer_type css_buf_type, enum ia_css_pipe_id css_pipe_id) @@ -1006,7 +947,7 @@ int atomisp_q_video_buffer_to_css(struct atomisp_sub_device *asd, int err; css_buf.type = css_buf_type; - css_buf.data.frame = vm_mem->vaddr; + css_buf.data.frame = frame; err = ia_css_pipe_enqueue_buffer( stream_env->pipes[css_pipe_id], &css_buf); @@ -2141,8 +2082,6 @@ static enum ia_css_pipe_mode __pipe_id_to_pipe_mode( return IA_CSS_PIPE_MODE_CAPTURE; case IA_CSS_PIPE_ID_VIDEO: return IA_CSS_PIPE_MODE_VIDEO; - case IA_CSS_PIPE_ID_ACC: - return IA_CSS_PIPE_MODE_ACC; case IA_CSS_PIPE_ID_YUVPP: return IA_CSS_PIPE_MODE_YUVPP; default: @@ -2688,7 +2627,7 @@ int atomisp_get_css_frame_info(struct atomisp_sub_device *asd, if (0 != ia_css_pipe_get_info(asd->stream_env[stream_index] .pipes[pipe_index], &info)) { - dev_err(isp->dev, "ia_css_pipe_get_info FAILED"); + dev_dbg(isp->dev, "ia_css_pipe_get_info FAILED"); return -EINVAL; } @@ -2765,29 +2704,6 @@ int atomisp_css_yuvpp_configure_output(struct atomisp_sub_device *asd, return 0; } -int atomisp_css_yuvpp_configure_viewfinder( - struct atomisp_sub_device *asd, - unsigned int stream_index, - unsigned int width, unsigned int height, - unsigned int min_width, - enum ia_css_frame_format format) -{ - struct atomisp_stream_env *stream_env = - &asd->stream_env[stream_index]; - enum ia_css_pipe_id pipe_id = IA_CSS_PIPE_ID_YUVPP; - - stream_env->pipe_configs[pipe_id].mode = - __pipe_id_to_pipe_mode(asd, pipe_id); - stream_env->update_pipe[pipe_id] = true; - - stream_env->pipe_configs[pipe_id].vf_output_info[0].res.width = width; - stream_env->pipe_configs[pipe_id].vf_output_info[0].res.height = height; - stream_env->pipe_configs[pipe_id].vf_output_info[0].format = format; - stream_env->pipe_configs[pipe_id].vf_output_info[0].padded_width = - min_width; - return 0; -} - int atomisp_css_yuvpp_get_output_frame_info( struct atomisp_sub_device *asd, unsigned int stream_index, @@ -3180,7 +3096,7 @@ static int atomisp_compare_dvs_grid(struct atomisp_sub_device *asd, } if (sizeof(*cur) != sizeof(*atomgrid)) { - dev_err(asd->isp->dev, "dvs grid mis-match!\n"); + dev_err(asd->isp->dev, "dvs grid mismatch!\n"); return -EINVAL; } diff --git a/drivers/staging/media/atomisp/pci/atomisp_fops.c b/drivers/staging/media/atomisp/pci/atomisp_fops.c index 84a84e0cdeef..acea7492847d 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_fops.c +++ b/drivers/staging/media/atomisp/pci/atomisp_fops.c @@ -22,7 +22,7 @@ #include <linux/pm_runtime.h> #include <media/v4l2-ioctl.h> -#include <media/videobuf-vmalloc.h> +#include <media/videobuf2-vmalloc.h> #include "atomisp_cmd.h" #include "atomisp_common.h" @@ -35,49 +35,74 @@ #include "atomisp-regs.h" #include "hmm/hmm.h" +#include "ia_css_frame.h" #include "type_support.h" #include "device_access/device_access.h" -#define ISP_LEFT_PAD 128 /* equal to 2*NWAY */ - /* - * input image data, and current frame resolution for test + * Videobuf2 ops */ -#define ISP_PARAM_MMAP_OFFSET 0xfffff000 +static int atomisp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct atomisp_video_pipe *pipe = container_of(vq, struct atomisp_video_pipe, vb_queue); + u16 source_pad = atomisp_subdev_source_pad(&pipe->vdev); + int ret; -#define MAGIC_CHECK(is, should) \ - do { \ - if (unlikely((is) != (should))) { \ - pr_err("magic mismatch: %x (expected %x)\n", \ - is, should); \ - BUG(); \ - } \ - } while (0) + mutex_lock(&pipe->asd->isp->mutex); /* for get_css_frame_info() / set_fmt() */ -/* - * Videobuf ops - */ -static int atomisp_buf_setup(struct videobuf_queue *vq, unsigned int *count, - unsigned int *size) -{ - struct atomisp_video_pipe *pipe = vq->priv_data; + /* + * When VIDIOC_S_FMT has not been called before VIDIOC_REQBUFS, then + * this will fail. Call atomisp_set_fmt() ourselves and try again. + */ + ret = atomisp_get_css_frame_info(pipe->asd, source_pad, &pipe->frame_info); + if (ret) { + struct v4l2_format f = { + .fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420, + .fmt.pix.width = 10000, + .fmt.pix.height = 10000, + }; + + ret = atomisp_set_fmt(&pipe->vdev, &f); + if (ret) + goto out; + + ret = atomisp_get_css_frame_info(pipe->asd, source_pad, &pipe->frame_info); + if (ret) + goto out; + } + + atomisp_alloc_css_stat_bufs(pipe->asd, ATOMISP_INPUT_STREAM_GENERAL); - *size = pipe->pix.sizeimage; + *nplanes = 1; + sizes[0] = PAGE_ALIGN(pipe->pix.sizeimage); +out: + mutex_unlock(&pipe->asd->isp->mutex); return 0; } -static int atomisp_buf_prepare(struct videobuf_queue *vq, - struct videobuf_buffer *vb, - enum v4l2_field field) +static int atomisp_buf_init(struct vb2_buffer *vb) { - struct atomisp_video_pipe *pipe = vq->priv_data; + struct atomisp_video_pipe *pipe = vb_to_pipe(vb); + struct ia_css_frame *frame = vb_to_frame(vb); + int ret; + + ret = ia_css_frame_init_from_info(frame, &pipe->frame_info); + if (ret) + return ret; - vb->size = pipe->pix.sizeimage; - vb->width = pipe->pix.width; - vb->height = pipe->pix.height; - vb->field = field; - vb->state = VIDEOBUF_PREPARED; + if (frame->data_bytes > vb2_plane_size(vb, 0)) { + dev_err(pipe->asd->isp->dev, "Internal error frame.data_bytes(%u) > vb.length(%lu)\n", + frame->data_bytes, vb2_plane_size(vb, 0)); + return -EIO; + } + + frame->data = hmm_create_from_vmalloc_buf(vb2_plane_size(vb, 0), + vb2_plane_vaddr(vb, 0)); + if (frame->data == mmgr_NULL) + return -ENOMEM; return 0; } @@ -156,8 +181,7 @@ static int atomisp_q_one_s3a_buffer(struct atomisp_sub_device *asd, } else { list_add_tail(&s3a_buf->list, &asd->s3a_stats_in_css); if (s3a_list == &asd->s3a_stats_ready) - dev_warn(asd->isp->dev, "%s: drop one s3a stat which has exp_id %d!\n", - __func__, exp_id); + dev_dbg(asd->isp->dev, "drop one s3a stat with exp_id %d\n", exp_id); } asd->s3a_bufs_in_css[css_pipe_id]++; @@ -206,43 +230,44 @@ static int atomisp_q_one_dis_buffer(struct atomisp_sub_device *asd, return 0; } -int atomisp_q_video_buffers_to_css(struct atomisp_sub_device *asd, - struct atomisp_video_pipe *pipe, - enum atomisp_input_stream_id stream_id, - enum ia_css_buffer_type css_buf_type, - enum ia_css_pipe_id css_pipe_id) +static int atomisp_q_video_buffers_to_css(struct atomisp_sub_device *asd, + struct atomisp_video_pipe *pipe, + enum atomisp_input_stream_id stream_id, + enum ia_css_buffer_type css_buf_type, + enum ia_css_pipe_id css_pipe_id) { - struct videobuf_vmalloc_memory *vm_mem; struct atomisp_css_params_with_list *param; struct ia_css_dvs_grid_info *dvs_grid = atomisp_css_get_dvs_grid_info(&asd->params.curr_grid_info); unsigned long irqflags; - int err = 0; + int space, err = 0; + + lockdep_assert_held(&asd->isp->mutex); if (WARN_ON(css_pipe_id >= IA_CSS_PIPE_ID_NUM)) return -EINVAL; - while (pipe->buffers_in_css < ATOMISP_CSS_Q_DEPTH) { - struct videobuf_buffer *vb; + if (pipe->stopping) + return -EINVAL; + + space = ATOMISP_CSS_Q_DEPTH - atomisp_buffers_in_css(pipe); + while (space--) { + struct ia_css_frame *frame; spin_lock_irqsave(&pipe->irq_lock, irqflags); - if (list_empty(&pipe->activeq)) { - spin_unlock_irqrestore(&pipe->irq_lock, irqflags); - return -EINVAL; - } - vb = list_entry(pipe->activeq.next, - struct videobuf_buffer, queue); - list_del_init(&vb->queue); - vb->state = VIDEOBUF_ACTIVE; + frame = list_first_entry_or_null(&pipe->activeq, struct ia_css_frame, queue); + if (frame) + list_move_tail(&frame->queue, &pipe->buffers_in_css); spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + if (!frame) + return -EINVAL; + /* * If there is a per_frame setting to apply on the buffer, * do it before buffer en-queueing. */ - vm_mem = vb->priv; - - param = pipe->frame_params[vb->i]; + param = pipe->frame_params[frame->vb.vb2_buf.index]; if (param) { atomisp_makeup_css_parameters(asd, &asd->params.css_param.update_flag, @@ -256,8 +281,7 @@ int atomisp_q_video_buffers_to_css(struct atomisp_sub_device *asd, if (!err) asd->params.config.dz_config = ¶m->params.dz_config; } - atomisp_css_set_isp_config_applied_frame(asd, - vm_mem->vaddr); + atomisp_css_set_isp_config_applied_frame(asd, frame); atomisp_css_update_isp_params_on_pipe(asd, asd->stream_env[stream_id].pipes[css_pipe_id]); asd->params.dvs_6axis = (struct ia_css_dvs_6axis_config *) @@ -282,20 +306,19 @@ int atomisp_q_video_buffers_to_css(struct atomisp_sub_device *asd, &asd->params.css_param.dz_config; asd->params.css_update_params_needed = true; } + pipe->frame_params[frame->vb.vb2_buf.index] = NULL; } /* Enqueue buffer */ - err = atomisp_q_video_buffer_to_css(asd, vm_mem, stream_id, + err = atomisp_q_video_buffer_to_css(asd, frame, stream_id, css_buf_type, css_pipe_id); if (err) { spin_lock_irqsave(&pipe->irq_lock, irqflags); - list_add_tail(&vb->queue, &pipe->activeq); - vb->state = VIDEOBUF_QUEUED; + list_move_tail(&frame->queue, &pipe->activeq); spin_unlock_irqrestore(&pipe->irq_lock, irqflags); dev_err(asd->isp->dev, "%s, css q fails: %d\n", __func__, err); return -EINVAL; } - pipe->buffers_in_css++; /* enqueue 3A/DIS/metadata buffers */ if (asd->params.curr_grid_info.s3a_grid.enable && @@ -517,11 +540,32 @@ int atomisp_qbuffers_to_css(struct atomisp_sub_device *asd) return 0; } -static void atomisp_buf_queue(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void atomisp_buf_queue(struct vb2_buffer *vb) { - struct atomisp_video_pipe *pipe = vq->priv_data; + struct atomisp_video_pipe *pipe = vb_to_pipe(vb); + struct ia_css_frame *frame = vb_to_frame(vb); + struct atomisp_sub_device *asd = pipe->asd; + u16 source_pad = atomisp_subdev_source_pad(&pipe->vdev); + unsigned long irqflags; + int ret; + + mutex_lock(&asd->isp->mutex); + + ret = atomisp_pipe_check(pipe, false); + if (ret || pipe->stopping) { + spin_lock_irqsave(&pipe->irq_lock, irqflags); + atomisp_buffer_done(frame, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + goto out_unlock; + } + + /* FIXME this ugliness comes from the original atomisp buffer handling */ + if (!(vb->skip_cache_sync_on_finish && vb->skip_cache_sync_on_prepare)) + wbinvd(); + pipe->frame_params[vb->index] = NULL; + + spin_lock_irqsave(&pipe->irq_lock, irqflags); /* * when a frame buffer meets following conditions, it should be put into * the waiting list: @@ -533,40 +577,83 @@ static void atomisp_buf_queue(struct videobuf_queue *vq, * get enqueued. */ if (!atomisp_is_vf_pipe(pipe) && - (pipe->frame_request_config_id[vb->i] || + (pipe->frame_request_config_id[vb->index] || !list_empty(&pipe->buffers_waiting_for_param))) - list_add_tail(&vb->queue, &pipe->buffers_waiting_for_param); + list_add_tail(&frame->queue, &pipe->buffers_waiting_for_param); else - list_add_tail(&vb->queue, &pipe->activeq); + list_add_tail(&frame->queue, &pipe->activeq); + + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + + /* TODO: do this better, not best way to queue to css */ + if (asd->streaming == ATOMISP_DEVICE_STREAMING_ENABLED) { + if (!list_empty(&pipe->buffers_waiting_for_param)) + atomisp_handle_parameter_and_buffer(pipe); + else + atomisp_qbuffers_to_css(asd); + } + + /* + * Workaround: Due to the design of HALv3, + * sometimes in ZSL or SDV mode HAL needs to + * capture multiple images within one streaming cycle. + * But the capture number cannot be determined by HAL. + * So HAL only sets the capture number to be 1 and queue multiple + * buffers. Atomisp driver needs to check this case and re-trigger + * CSS to do capture when new buffer is queued. + */ + if (asd->continuous_mode->val && source_pad == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE && + !asd->enable_raw_buffer_lock->val && asd->params.offline_parm.num_captures == 1) { + asd->pending_capture_request++; + dev_dbg(asd->isp->dev, "Add one pending capture request.\n"); + } - vb->state = VIDEOBUF_QUEUED; +out_unlock: + mutex_unlock(&asd->isp->mutex); } -static void atomisp_buf_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void atomisp_buf_cleanup(struct vb2_buffer *vb) { - vb->state = VIDEOBUF_NEEDS_INIT; - atomisp_videobuf_free_buf(vb); + struct atomisp_video_pipe *pipe = vb_to_pipe(vb); + struct ia_css_frame *frame = vb_to_frame(vb); + int index = frame->vb.vb2_buf.index; + + pipe->frame_request_config_id[index] = 0; + pipe->frame_params[index] = NULL; + + hmm_free(frame->data); } -static const struct videobuf_queue_ops videobuf_qops = { - .buf_setup = atomisp_buf_setup, - .buf_prepare = atomisp_buf_prepare, - .buf_queue = atomisp_buf_queue, - .buf_release = atomisp_buf_release, +static const struct vb2_ops atomisp_vb2_ops = { + .queue_setup = atomisp_queue_setup, + .buf_init = atomisp_buf_init, + .buf_cleanup = atomisp_buf_cleanup, + .buf_queue = atomisp_buf_queue, + .start_streaming = atomisp_start_streaming, + .stop_streaming = atomisp_stop_streaming, }; static int atomisp_init_pipe(struct atomisp_video_pipe *pipe) { + int ret; + /* init locks */ spin_lock_init(&pipe->irq_lock); + mutex_init(&pipe->vb_queue_mutex); + + /* Init videobuf2 queue structure */ + pipe->vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pipe->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR; + pipe->vb_queue.buf_struct_size = sizeof(struct ia_css_frame); + pipe->vb_queue.ops = &atomisp_vb2_ops; + pipe->vb_queue.mem_ops = &vb2_vmalloc_memops; + pipe->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&pipe->vb_queue); + if (ret) + return ret; - videobuf_queue_vmalloc_init(&pipe->capq, &videobuf_qops, NULL, - &pipe->irq_lock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, - V4L2_FIELD_NONE, - sizeof(struct atomisp_buffer), pipe, - NULL); /* ext_lock: NULL */ + pipe->vdev.queue = &pipe->vb_queue; + pipe->vdev.queue->lock = &pipe->vb_queue_mutex; INIT_LIST_HEAD(&pipe->activeq); INIT_LIST_HEAD(&pipe->buffers_waiting_for_param); @@ -721,13 +808,6 @@ static int atomisp_open(struct file *file) goto error; } - /* Init ISP */ - if (atomisp_css_init(isp)) { - ret = -EINVAL; - /* Need to clean up CSS init if it fails. */ - goto css_error; - } - atomisp_dev_init_struct(isp); ret = v4l2_subdev_call(isp->flash, core, s_power, 1); @@ -752,7 +832,6 @@ done: return 0; css_error: - atomisp_css_uninit(isp); pm_runtime_put(vdev->v4l2_dev->dev); error: mutex_unlock(&isp->mutex); @@ -766,45 +845,26 @@ static int atomisp_release(struct file *file) struct atomisp_device *isp = video_get_drvdata(vdev); struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); struct atomisp_sub_device *asd = pipe->asd; - struct v4l2_requestbuffers req; struct v4l2_subdev_fh fh; struct v4l2_rect clear_compose = {0}; unsigned long flags; - int ret = 0; + int ret; v4l2_fh_init(&fh.vfh, vdev); - req.count = 0; - if (!isp) - return -EBADF; - - mutex_lock(&isp->mutex); - dev_dbg(isp->dev, "release device %s\n", vdev->name); asd->subdev.devnode = vdev; - pipe->users--; - - if (pipe->capq.streaming) - dev_warn(isp->dev, - "%s: ISP still streaming while closing!", - __func__); + /* Note file must not be used after this! */ + vb2_fop_release(file); - if (pipe->capq.streaming && - atomisp_streamoff(file, NULL, V4L2_BUF_TYPE_VIDEO_CAPTURE)) { - dev_err(isp->dev, "atomisp_streamoff failed on release, driver bug"); - goto done; - } + mutex_lock(&isp->mutex); + pipe->users--; if (pipe->users) goto done; - if (atomisp_reqbufs(file, NULL, &req)) { - dev_err(isp->dev, "atomisp_reqbufs failed on release, driver bug"); - goto done; - } - /* * A little trick here: * file injection input resolution is recorded in the sink pad, @@ -840,7 +900,6 @@ static int atomisp_release(struct file *file) goto done; atomisp_destroy_pipes_stream_force(asd); - atomisp_css_uninit(isp); if (defer_fw_load) { ia_css_unload_firmware(); @@ -862,260 +921,15 @@ done: V4L2_SEL_TGT_COMPOSE, 0, &clear_compose); mutex_unlock(&isp->mutex); - - return v4l2_fh_release(file); -} - -/* - * Memory help functions for image frame and private parameters - */ -static int do_isp_mm_remap(struct atomisp_device *isp, - struct vm_area_struct *vma, - ia_css_ptr isp_virt, u32 host_virt, u32 pgnr) -{ - u32 pfn; - - while (pgnr) { - pfn = hmm_virt_to_phys(isp_virt) >> PAGE_SHIFT; - if (remap_pfn_range(vma, host_virt, pfn, - PAGE_SIZE, PAGE_SHARED)) { - dev_err(isp->dev, "remap_pfn_range err.\n"); - return -EAGAIN; - } - - isp_virt += PAGE_SIZE; - host_virt += PAGE_SIZE; - pgnr--; - } - - return 0; -} - -static int frame_mmap(struct atomisp_device *isp, - const struct ia_css_frame *frame, struct vm_area_struct *vma) -{ - ia_css_ptr isp_virt; - u32 host_virt; - u32 pgnr; - - if (!frame) { - dev_err(isp->dev, "%s: NULL frame pointer.\n", __func__); - return -EINVAL; - } - - host_virt = vma->vm_start; - isp_virt = frame->data; - pgnr = DIV_ROUND_UP(frame->data_bytes, PAGE_SIZE); - - if (do_isp_mm_remap(isp, vma, isp_virt, host_virt, pgnr)) - return -EAGAIN; - return 0; } -int atomisp_videobuf_mmap_mapper(struct videobuf_queue *q, - struct vm_area_struct *vma) -{ - u32 offset = vma->vm_pgoff << PAGE_SHIFT; - int ret = -EINVAL, i; - struct atomisp_device *isp = - ((struct atomisp_video_pipe *)(q->priv_data))->isp; - struct videobuf_vmalloc_memory *vm_mem; - struct videobuf_mapping *map; - - MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); - if (!(vma->vm_flags & VM_WRITE) || !(vma->vm_flags & VM_SHARED)) { - dev_err(isp->dev, "map appl bug: PROT_WRITE and MAP_SHARED are required\n"); - return -EINVAL; - } - - mutex_lock(&q->vb_lock); - for (i = 0; i < VIDEO_MAX_FRAME; i++) { - struct videobuf_buffer *buf = q->bufs[i]; - - if (!buf) - continue; - - map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); - if (!map) { - mutex_unlock(&q->vb_lock); - return -ENOMEM; - } - - buf->map = map; - map->q = q; - - buf->baddr = vma->vm_start; - - if (buf && buf->memory == V4L2_MEMORY_MMAP && - buf->boff == offset) { - vm_mem = buf->priv; - ret = frame_mmap(isp, vm_mem->vaddr, vma); - vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; - break; - } - } - mutex_unlock(&q->vb_lock); - - return ret; -} - -/* The input frame contains left and right padding that need to be removed. - * There is always ISP_LEFT_PAD padding on the left side. - * There is also padding on the right (padded_width - width). - */ -static int remove_pad_from_frame(struct atomisp_device *isp, - struct ia_css_frame *in_frame, __u32 width, __u32 height) -{ - unsigned int i; - unsigned short *buffer; - int ret = 0; - ia_css_ptr load = in_frame->data; - ia_css_ptr store = load; - - buffer = kmalloc_array(width, sizeof(load), GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - load += ISP_LEFT_PAD; - for (i = 0; i < height; i++) { - ret = hmm_load(load, buffer, width * sizeof(load)); - if (ret < 0) - goto remove_pad_error; - - ret = hmm_store(store, buffer, width * sizeof(store)); - if (ret < 0) - goto remove_pad_error; - - load += in_frame->info.padded_width; - store += width; - } - -remove_pad_error: - kfree(buffer); - return ret; -} - -static int atomisp_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct video_device *vdev = video_devdata(file); - struct atomisp_device *isp = video_get_drvdata(vdev); - struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); - struct atomisp_sub_device *asd = pipe->asd; - struct ia_css_frame *raw_virt_addr; - u32 start = vma->vm_start; - u32 end = vma->vm_end; - u32 size = end - start; - u32 origin_size, new_size; - int ret; - - if (!asd) { - dev_err(isp->dev, "%s(): asd is NULL, device is %s\n", - __func__, vdev->name); - return -EINVAL; - } - - if (!(vma->vm_flags & (VM_WRITE | VM_READ))) - return -EACCES; - - mutex_lock(&isp->mutex); - - if (!(vma->vm_flags & VM_SHARED)) { - /* Map private buffer. - * Set VM_SHARED to the flags since we need - * to map the buffer page by page. - * Without VM_SHARED, remap_pfn_range() treats - * this kind of mapping as invalid. - */ - vma->vm_flags |= VM_SHARED; - ret = hmm_mmap(vma, vma->vm_pgoff << PAGE_SHIFT); - mutex_unlock(&isp->mutex); - return ret; - } - - /* mmap for ISP offline raw data */ - if (atomisp_subdev_source_pad(vdev) - == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE && - vma->vm_pgoff == (ISP_PARAM_MMAP_OFFSET >> PAGE_SHIFT)) { - new_size = pipe->pix.width * pipe->pix.height * 2; - if (asd->params.online_process != 0) { - ret = -EINVAL; - goto error; - } - raw_virt_addr = asd->raw_output_frame; - if (!raw_virt_addr) { - dev_err(isp->dev, "Failed to request RAW frame\n"); - ret = -EINVAL; - goto error; - } - - ret = remove_pad_from_frame(isp, raw_virt_addr, - pipe->pix.width, pipe->pix.height); - if (ret < 0) { - dev_err(isp->dev, "remove pad failed.\n"); - goto error; - } - origin_size = raw_virt_addr->data_bytes; - raw_virt_addr->data_bytes = new_size; - - if (size != PAGE_ALIGN(new_size)) { - dev_err(isp->dev, "incorrect size for mmap ISP Raw Frame\n"); - ret = -EINVAL; - goto error; - } - - if (frame_mmap(isp, raw_virt_addr, vma)) { - dev_err(isp->dev, "frame_mmap failed.\n"); - raw_virt_addr->data_bytes = origin_size; - ret = -EAGAIN; - goto error; - } - raw_virt_addr->data_bytes = origin_size; - vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; - mutex_unlock(&isp->mutex); - return 0; - } - - /* - * mmap for normal frames - */ - if (size != pipe->pix.sizeimage) { - dev_err(isp->dev, "incorrect size for mmap ISP frames\n"); - ret = -EINVAL; - goto error; - } - mutex_unlock(&isp->mutex); - - return atomisp_videobuf_mmap_mapper(&pipe->capq, vma); - -error: - mutex_unlock(&isp->mutex); - - return ret; -} - -static __poll_t atomisp_poll(struct file *file, - struct poll_table_struct *pt) -{ - struct video_device *vdev = video_devdata(file); - struct atomisp_device *isp = video_get_drvdata(vdev); - struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); - - mutex_lock(&isp->mutex); - if (pipe->capq.streaming != 1) { - mutex_unlock(&isp->mutex); - return EPOLLERR; - } - mutex_unlock(&isp->mutex); - - return videobuf_poll_stream(file, &pipe->capq, pt); -} - const struct v4l2_file_operations atomisp_fops = { .owner = THIS_MODULE, .open = atomisp_open, .release = atomisp_release, - .mmap = atomisp_mmap, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, .unlocked_ioctl = video_ioctl2, #ifdef CONFIG_COMPAT /* @@ -1124,5 +938,4 @@ const struct v4l2_file_operations atomisp_fops = { .compat_ioctl32 = atomisp_compat_ioctl32, */ #endif - .poll = atomisp_poll, }; diff --git a/drivers/staging/media/atomisp/pci/atomisp_fops.h b/drivers/staging/media/atomisp/pci/atomisp_fops.h index 3f1e442ba782..10e43126b693 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_fops.h +++ b/drivers/staging/media/atomisp/pci/atomisp_fops.h @@ -22,12 +22,6 @@ #define __ATOMISP_FOPS_H__ #include "atomisp_subdev.h" -int atomisp_q_video_buffers_to_css(struct atomisp_sub_device *asd, - struct atomisp_video_pipe *pipe, - enum atomisp_input_stream_id stream_id, - enum ia_css_buffer_type css_buf_type, - enum ia_css_pipe_id css_pipe_id); - unsigned int atomisp_dev_users(struct atomisp_device *isp); unsigned int atomisp_sub_dev_users(struct atomisp_sub_device *asd); @@ -35,13 +29,6 @@ unsigned int atomisp_sub_dev_users(struct atomisp_sub_device *asd); * Memory help functions for image frame and private parameters */ -int atomisp_videobuf_mmap_mapper(struct videobuf_queue *q, - struct vm_area_struct *vma); - -int atomisp_qbuf_to_css(struct atomisp_device *isp, - struct atomisp_video_pipe *pipe, - struct videobuf_buffer *vb); - int atomisp_qbuffers_to_css(struct atomisp_sub_device *asd); extern const struct v4l2_file_operations atomisp_fops; diff --git a/drivers/staging/media/atomisp/pci/atomisp_internal.h b/drivers/staging/media/atomisp/pci/atomisp_internal.h index d9d158cdf09e..653e6d74a966 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_internal.h +++ b/drivers/staging/media/atomisp/pci/atomisp_internal.h @@ -195,7 +195,6 @@ struct atomisp_regs { }; struct atomisp_sw_contex { - int power_state; int running_freq; }; diff --git a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c index 0ddb0ed42dd9..cb01ba65c88f 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c +++ b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c @@ -23,7 +23,6 @@ #include <media/v4l2-ioctl.h> #include <media/v4l2-event.h> -#include <media/videobuf-vmalloc.h> #include "atomisp_cmd.h" #include "atomisp_common.h" @@ -542,6 +541,11 @@ int atomisp_pipe_check(struct atomisp_video_pipe *pipe, bool settings_change) if (pipe->isp->isp_fatal_error) return -EIO; + if (settings_change && vb2_is_busy(&pipe->vb_queue)) { + dev_err(pipe->isp->dev, "Set fmt/input IOCTL while streaming\n"); + return -EBUSY; + } + switch (pipe->asd->streaming) { case ATOMISP_DEVICE_STREAMING_DISABLED: break; @@ -632,10 +636,10 @@ static int atomisp_enum_input(struct file *file, void *fh, static unsigned int atomisp_subdev_streaming_count(struct atomisp_sub_device *asd) { - return asd->video_out_preview.capq.streaming - + asd->video_out_capture.capq.streaming - + asd->video_out_video_capture.capq.streaming - + asd->video_out_vf.capq.streaming; + return asd->video_out_preview.vb_queue.start_streaming_called + + asd->video_out_capture.vb_queue.start_streaming_called + + asd->video_out_video_capture.vb_queue.start_streaming_called + + asd->video_out_vf.vb_queue.start_streaming_called; } unsigned int atomisp_streaming_count(struct atomisp_device *isp) @@ -661,6 +665,14 @@ static int atomisp_g_input(struct file *file, void *fh, unsigned int *input) return 0; } +static int atomisp_s_fmt_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct video_device *vdev = video_devdata(file); + + return atomisp_set_fmt(vdev, f); +} + /* * set input are used to set current primary/secondary camera */ @@ -866,29 +878,8 @@ static int atomisp_adjust_fmt(struct v4l2_format *f) u32 padded_width; format_bridge = atomisp_get_format_bridge(f->fmt.pix.pixelformat); - - padded_width = f->fmt.pix.width + pad_w; - - if (format_bridge->planar) { - f->fmt.pix.bytesperline = padded_width; - f->fmt.pix.sizeimage = PAGE_ALIGN(f->fmt.pix.height * - DIV_ROUND_UP(format_bridge->depth * - padded_width, 8)); - } else { - f->fmt.pix.bytesperline = DIV_ROUND_UP(format_bridge->depth * - padded_width, 8); - f->fmt.pix.sizeimage = PAGE_ALIGN(f->fmt.pix.height * f->fmt.pix.bytesperline); - } - - if (f->fmt.pix.field == V4L2_FIELD_ANY) - f->fmt.pix.field = V4L2_FIELD_NONE; - - format_bridge = atomisp_get_format_bridge(f->fmt.pix.pixelformat); - if (!format_bridge) - return -EINVAL; - /* Currently, raw formats are broken!!! */ - if (format_bridge->sh_fmt == IA_CSS_FRAME_FORMAT_RAW) { + if (!format_bridge || format_bridge->sh_fmt == IA_CSS_FRAME_FORMAT_RAW) { f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; format_bridge = atomisp_get_format_bridge(f->fmt.pix.pixelformat); @@ -931,6 +922,7 @@ static int atomisp_try_fmt_cap(struct file *file, void *fh, struct v4l2_format *f) { struct video_device *vdev = video_devdata(file); + u32 pixfmt = f->fmt.pix.pixelformat; int ret; /* @@ -944,6 +936,12 @@ static int atomisp_try_fmt_cap(struct file *file, void *fh, if (ret) return ret; + /* + * atomisp_try_fmt() replaces pixelformat with the sensors native + * format, restore the actual format requested by userspace. + */ + f->fmt.pix.pixelformat = pixfmt; + return atomisp_adjust_fmt(f); } @@ -961,44 +959,13 @@ static int atomisp_g_fmt_cap(struct file *file, void *fh, if (f->fmt.pix.sizeimage) return 0; - f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; f->fmt.pix.width = 10000; f->fmt.pix.height = 10000; return atomisp_try_fmt_cap(file, fh, f); } -/* - * Free videobuffer buffer priv data - */ -void atomisp_videobuf_free_buf(struct videobuf_buffer *vb) -{ - struct videobuf_vmalloc_memory *vm_mem; - - if (!vb) - return; - - vm_mem = vb->priv; - if (vm_mem && vm_mem->vaddr) { - ia_css_frame_free(vm_mem->vaddr); - vm_mem->vaddr = NULL; - } -} - -/* - * this function is used to free video buffer queue - */ -static void atomisp_videobuf_free_queue(struct videobuf_queue *q) -{ - int i; - - for (i = 0; i < VIDEO_MAX_FRAME; i++) { - atomisp_videobuf_free_buf(q->bufs[i]); - kfree(q->bufs[i]); - q->bufs[i] = NULL; - } -} - int atomisp_alloc_css_stat_bufs(struct atomisp_sub_device *asd, uint16_t stream_id) { @@ -1100,178 +1067,13 @@ error: return -ENOMEM; } -/* - * Initiate Memory Mapping or User Pointer I/O - */ -int atomisp_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *req) -{ - struct video_device *vdev = video_devdata(file); - struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); - struct atomisp_sub_device *asd = pipe->asd; - struct ia_css_frame_info frame_info; - struct ia_css_frame *frame; - struct videobuf_vmalloc_memory *vm_mem; - u16 source_pad = atomisp_subdev_source_pad(vdev); - int ret = 0, i = 0; - - if (req->count == 0) { - mutex_lock(&pipe->capq.vb_lock); - if (!list_empty(&pipe->capq.stream)) - videobuf_queue_cancel(&pipe->capq); - - atomisp_videobuf_free_queue(&pipe->capq); - mutex_unlock(&pipe->capq.vb_lock); - /* clear request config id */ - memset(pipe->frame_request_config_id, 0, - VIDEO_MAX_FRAME * sizeof(unsigned int)); - memset(pipe->frame_params, 0, - VIDEO_MAX_FRAME * - sizeof(struct atomisp_css_params_with_list *)); - return 0; - } - - ret = videobuf_reqbufs(&pipe->capq, req); - if (ret) - return ret; - - atomisp_alloc_css_stat_bufs(asd, ATOMISP_INPUT_STREAM_GENERAL); - - /* - * for user pointer type, buffers are not really allocated here, - * buffers are setup in QBUF operation through v4l2_buffer structure - */ - if (req->memory == V4L2_MEMORY_USERPTR) - return 0; - - ret = atomisp_get_css_frame_info(asd, source_pad, &frame_info); - if (ret) - return ret; - - /* - * Allocate the real frame here for selected node using our - * memory management function - */ - for (i = 0; i < req->count; i++) { - if (ia_css_frame_allocate_from_info(&frame, &frame_info)) - goto error; - vm_mem = pipe->capq.bufs[i]->priv; - vm_mem->vaddr = frame; - } - - return ret; - -error: - while (i--) { - vm_mem = pipe->capq.bufs[i]->priv; - ia_css_frame_free(vm_mem->vaddr); - } - - if (asd->vf_frame) - ia_css_frame_free(asd->vf_frame); - - return -ENOMEM; -} - -/* application query the status of a buffer */ -static int atomisp_querybuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct video_device *vdev = video_devdata(file); - struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); - - return videobuf_querybuf(&pipe->capq, buf); -} - -/* - * Applications call the VIDIOC_QBUF ioctl to enqueue an empty (capturing) or - * filled (output) buffer in the drivers incoming queue. - */ -static int atomisp_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf) +static int atomisp_qbuf_wrapper(struct file *file, void *fh, struct v4l2_buffer *buf) { - static const int NOFLUSH_FLAGS = V4L2_BUF_FLAG_NO_CACHE_INVALIDATE | - V4L2_BUF_FLAG_NO_CACHE_CLEAN; struct video_device *vdev = video_devdata(file); struct atomisp_device *isp = video_get_drvdata(vdev); struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); - struct atomisp_sub_device *asd = pipe->asd; - struct videobuf_buffer *vb; - struct videobuf_vmalloc_memory *vm_mem; - struct ia_css_frame_info frame_info; - struct ia_css_frame *handle = NULL; - u32 length; - u32 pgnr; - int ret; - - ret = atomisp_pipe_check(pipe, false); - if (ret) - return ret; - - if (!buf || buf->index >= VIDEO_MAX_FRAME || - !pipe->capq.bufs[buf->index]) { - dev_err(isp->dev, "Invalid index for qbuf.\n"); - return -EINVAL; - } - - /* - * For userptr type frame, we convert user space address to physic - * address and reprograme out page table properly - */ - if (buf->memory == V4L2_MEMORY_USERPTR) { - if (offset_in_page(buf->m.userptr)) { - dev_err(isp->dev, "Error userptr is not page aligned.\n"); - return -EINVAL; - } - - vb = pipe->capq.bufs[buf->index]; - vm_mem = vb->priv; - if (!vm_mem) - return -EINVAL; - - length = vb->bsize; - pgnr = (length + (PAGE_SIZE - 1)) >> PAGE_SHIFT; - - if (vb->baddr == buf->m.userptr && vm_mem->vaddr) - goto done; - - if (atomisp_get_css_frame_info(asd, - atomisp_subdev_source_pad(vdev), &frame_info)) - return -EIO; - - ret = ia_css_frame_map(&handle, &frame_info, - (void __user *)buf->m.userptr, - pgnr); - if (ret) { - dev_err(isp->dev, "Failed to map user buffer\n"); - return ret; - } - - if (vm_mem->vaddr) { - mutex_lock(&pipe->capq.vb_lock); - ia_css_frame_free(vm_mem->vaddr); - vm_mem->vaddr = NULL; - vb->state = VIDEOBUF_NEEDS_INIT; - mutex_unlock(&pipe->capq.vb_lock); - } - - vm_mem->vaddr = handle; - - buf->flags &= ~V4L2_BUF_FLAG_MAPPED; - buf->flags |= V4L2_BUF_FLAG_QUEUED; - buf->flags &= ~V4L2_BUF_FLAG_DONE; - } else if (buf->memory == V4L2_MEMORY_MMAP) { - buf->flags |= V4L2_BUF_FLAG_MAPPED; - buf->flags |= V4L2_BUF_FLAG_QUEUED; - buf->flags &= ~V4L2_BUF_FLAG_DONE; - - /* - * For mmap, frames were allocated at request buffers - */ - } - -done: - if (!((buf->flags & NOFLUSH_FLAGS) == NOFLUSH_FLAGS)) - wbinvd(); + /* FIXME this abuse of buf->reserved2 comes from the original atomisp buffer handling */ if (!atomisp_is_vf_pipe(pipe) && (buf->reserved2 & ATOMISP_BUFFER_HAS_PER_FRAME_SETTING)) { /* this buffer will have a per-frame parameter */ @@ -1284,90 +1086,27 @@ done: pipe->frame_request_config_id[buf->index] = 0; } - pipe->frame_params[buf->index] = NULL; - - mutex_unlock(&isp->mutex); - ret = videobuf_qbuf(&pipe->capq, buf); - mutex_lock(&isp->mutex); - if (ret) - return ret; - - /* TODO: do this better, not best way to queue to css */ - if (asd->streaming == ATOMISP_DEVICE_STREAMING_ENABLED) { - if (!list_empty(&pipe->buffers_waiting_for_param)) { - atomisp_handle_parameter_and_buffer(pipe); - } else { - atomisp_qbuffers_to_css(asd); - } - } - - /* - * Workaround: Due to the design of HALv3, - * sometimes in ZSL or SDV mode HAL needs to - * capture multiple images within one streaming cycle. - * But the capture number cannot be determined by HAL. - * So HAL only sets the capture number to be 1 and queue multiple - * buffers. Atomisp driver needs to check this case and re-trigger - * CSS to do capture when new buffer is queued. - */ - if (asd->continuous_mode->val && - atomisp_subdev_source_pad(vdev) - == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE && - pipe->capq.streaming && - !asd->enable_raw_buffer_lock->val && - asd->params.offline_parm.num_captures == 1) { - asd->pending_capture_request++; - dev_dbg(isp->dev, "Add one pending capture request.\n"); - } - - dev_dbg(isp->dev, "qbuf buffer %d (%s) for asd%d\n", buf->index, - vdev->name, asd->index); - - return 0; + return vb2_ioctl_qbuf(file, fh, buf); } -static int __get_frame_exp_id(struct atomisp_video_pipe *pipe, - struct v4l2_buffer *buf) -{ - struct videobuf_vmalloc_memory *vm_mem; - struct ia_css_frame *handle; - int i; - - for (i = 0; pipe->capq.bufs[i]; i++) { - vm_mem = pipe->capq.bufs[i]->priv; - handle = vm_mem->vaddr; - if (buf->index == pipe->capq.bufs[i]->i && handle) - return handle->exp_id; - } - return -EINVAL; -} - -/* - * Applications call the VIDIOC_DQBUF ioctl to dequeue a filled (capturing) or - * displayed (output buffer)from the driver's outgoing queue - */ -static int atomisp_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) +static int atomisp_dqbuf_wrapper(struct file *file, void *fh, struct v4l2_buffer *buf) { struct video_device *vdev = video_devdata(file); struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); struct atomisp_sub_device *asd = pipe->asd; struct atomisp_device *isp = video_get_drvdata(vdev); + struct ia_css_frame *frame; + struct vb2_buffer *vb; int ret; - ret = atomisp_pipe_check(pipe, false); + ret = vb2_ioctl_dqbuf(file, fh, buf); if (ret) return ret; - mutex_unlock(&isp->mutex); - ret = videobuf_dqbuf(&pipe->capq, buf, file->f_flags & O_NONBLOCK); - mutex_lock(&isp->mutex); - if (ret) { - if (ret != -EAGAIN) - dev_dbg(isp->dev, "<%s: %d\n", __func__, ret); - return ret; - } + vb = pipe->vb_queue.bufs[buf->index]; + frame = vb_to_frame(vb); - buf->bytesused = pipe->pix.sizeimage; + /* FIXME this abuse of buf->reserved* comes from the original atomisp buffer handling */ buf->reserved = asd->frame_status[buf->index]; /* @@ -1378,7 +1117,7 @@ static int atomisp_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) */ buf->reserved &= 0x0000ffff; if (!(buf->flags & V4L2_BUF_FLAG_ERROR)) - buf->reserved |= __get_frame_exp_id(pipe, buf) << 16; + buf->reserved |= frame->exp_id; buf->reserved2 = pipe->frame_config_id[buf->index]; dev_dbg(isp->dev, @@ -1506,36 +1245,26 @@ static void atomisp_dma_burst_len_cfg(struct atomisp_sub_device *asd) atomisp_css2_hw_store_32(DMA_BURST_SIZE_REG, 0x00); } -/* - * This ioctl start the capture during streaming I/O. - */ -static int atomisp_streamon(struct file *file, void *fh, - enum v4l2_buf_type type) +int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count) { - struct video_device *vdev = video_devdata(file); - struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); + struct atomisp_video_pipe *pipe = vq_to_pipe(vq); struct atomisp_sub_device *asd = pipe->asd; - struct atomisp_device *isp = video_get_drvdata(vdev); + struct video_device *vdev = &pipe->vdev; + struct atomisp_device *isp = asd->isp; struct pci_dev *pdev = to_pci_dev(isp->dev); enum ia_css_pipe_id css_pipe_id; unsigned int sensor_start_stream; unsigned long irqflags; int ret; + mutex_lock(&isp->mutex); + dev_dbg(isp->dev, "Start stream on pad %d for asd%d\n", atomisp_subdev_source_pad(vdev), asd->index); - if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { - dev_dbg(isp->dev, "unsupported v4l2 buf type\n"); - return -EINVAL; - } - ret = atomisp_pipe_check(pipe, false); if (ret) - return ret; - - if (pipe->capq.streaming) - return 0; + goto out_unlock; /* Input system HW workaround */ atomisp_dma_burst_len_cfg(asd); @@ -1546,18 +1275,6 @@ static int atomisp_streamon(struct file *file, void *fh, */ sensor_start_stream = atomisp_sensor_start_stream(asd); - spin_lock_irqsave(&pipe->irq_lock, irqflags); - if (list_empty(&pipe->capq.stream)) { - spin_unlock_irqrestore(&pipe->irq_lock, irqflags); - dev_dbg(isp->dev, "no buffer in the queue\n"); - return -EINVAL; - } - spin_unlock_irqrestore(&pipe->irq_lock, irqflags); - - ret = videobuf_streamon(&pipe->capq); - if (ret) - return ret; - /* Reset pending capture request count. */ asd->pending_capture_request = 0; @@ -1578,8 +1295,10 @@ static int atomisp_streamon(struct file *file, void *fh, mutex_unlock(&isp->mutex); ret = wait_for_completion_interruptible(&asd->init_done); mutex_lock(&isp->mutex); - if (ret != 0) - return -ERESTARTSYS; + if (ret) { + ret = -ERESTARTSYS; + goto out_unlock; + } } /* handle per_frame_setting parameter and buffers */ @@ -1601,12 +1320,15 @@ static int atomisp_streamon(struct file *file, void *fh, asd->params.offline_parm.num_captures, asd->params.offline_parm.skip_frames, asd->params.offline_parm.offset); - if (ret) - return -EINVAL; + if (ret) { + ret = -EINVAL; + goto out_unlock; + } } } atomisp_qbuffers_to_css(asd); - return 0; + ret = 0; + goto out_unlock; } if (asd->streaming == ATOMISP_DEVICE_STREAMING_ENABLED) { @@ -1631,8 +1353,10 @@ static int atomisp_streamon(struct file *file, void *fh, asd->params.dvs_6axis = NULL; ret = atomisp_css_start(asd, css_pipe_id, false); - if (ret) - return ret; + if (ret) { + atomisp_flush_video_pipe(pipe, true); + goto out_unlock; + } spin_lock_irqsave(&isp->lock, irqflags); asd->streaming = ATOMISP_DEVICE_STREAMING_ENABLED; @@ -1652,8 +1376,10 @@ static int atomisp_streamon(struct file *file, void *fh, atomisp_qbuffers_to_css(asd); /* Only start sensor when the last streaming instance started */ - if (atomisp_subdev_streaming_count(asd) < sensor_start_stream) - return 0; + if (atomisp_subdev_streaming_count(asd) < sensor_start_stream) { + ret = 0; + goto out_unlock; + } start_sensor: if (isp->flash) { @@ -1684,7 +1410,7 @@ start_sensor: ret = atomisp_stream_on_master_slave_sensor(isp, false); if (ret) { dev_err(isp->dev, "master slave sensor stream on failed!\n"); - return ret; + goto out_unlock; } goto start_delay_wq; } else if (asd->depth_mode->val && (atomisp_streaming_count(isp) < @@ -1706,7 +1432,8 @@ start_sensor: spin_lock_irqsave(&isp->lock, irqflags); asd->streaming = ATOMISP_DEVICE_STREAMING_DISABLED; spin_unlock_irqrestore(&isp->lock, irqflags); - return -EINVAL; + ret = -EINVAL; + goto out_unlock; } start_delay_wq: @@ -1722,35 +1449,44 @@ start_delay_wq: asd->delayed_init = ATOMISP_DELAYED_INIT_NOT_QUEUED; } - return 0; +out_unlock: + mutex_unlock(&isp->mutex); + return ret; } -int atomisp_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) +void atomisp_stop_streaming(struct vb2_queue *vq) { - struct video_device *vdev = video_devdata(file); - struct atomisp_device *isp = video_get_drvdata(vdev); - struct pci_dev *pdev = to_pci_dev(isp->dev); - struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); + struct atomisp_video_pipe *pipe = vq_to_pipe(vq); struct atomisp_sub_device *asd = pipe->asd; - struct atomisp_video_pipe *capture_pipe = NULL; - struct atomisp_video_pipe *vf_pipe = NULL; - struct atomisp_video_pipe *preview_pipe = NULL; - struct atomisp_video_pipe *video_pipe = NULL; - struct videobuf_buffer *vb, *_vb; + struct video_device *vdev = &pipe->vdev; + struct atomisp_device *isp = asd->isp; + struct pci_dev *pdev = to_pci_dev(isp->dev); + bool recreate_streams[MAX_STREAM_NUM] = {0}; enum ia_css_pipe_id css_pipe_id; - int ret; - unsigned long flags; bool first_streamoff = false; + unsigned long flags; + int i, ret; + + mutex_lock(&isp->mutex); dev_dbg(isp->dev, "Stop stream on pad %d for asd%d\n", atomisp_subdev_source_pad(vdev), asd->index); - lockdep_assert_held(&isp->mutex); - - if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { - dev_dbg(isp->dev, "unsupported v4l2 buf type\n"); - return -EINVAL; - } + /* + * There is no guarantee that the buffers queued to / owned by the ISP + * will properly be returned to the queue when stopping. Set a flag to + * avoid new buffers getting queued and then wait for all the current + * buffers to finish. + */ + pipe->stopping = true; + mutex_unlock(&isp->mutex); + /* wait max 1 second */ + ret = wait_event_timeout(pipe->vb_queue.done_wq, + atomisp_buffers_in_css(pipe) == 0, HZ); + mutex_lock(&isp->mutex); + pipe->stopping = false; + if (ret == 0) + dev_warn(isp->dev, "Warning timeout waiting for CSS to return buffers\n"); /* * do only videobuf_streamoff for capture & vf pipes in @@ -1766,36 +1502,10 @@ int atomisp_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) 0, 0, 0); atomisp_freq_scaling(isp, ATOMISP_DFS_MODE_AUTO, false); } - /* - * Currently there is no way to flush buffers queued to css. - * When doing videobuf_streamoff, active buffers will be - * marked as VIDEOBUF_NEEDS_INIT. HAL will be able to use - * these buffers again, and these buffers might be queued to - * css more than once! Warn here, if HAL has not dequeued all - * buffers back before calling streamoff. - */ - if (pipe->buffers_in_css != 0) { - WARN(1, "%s: buffers of vdev %s still in CSS!\n", - __func__, pipe->vdev.name); - - /* - * Buffers remained in css maybe dequeued out in the - * next stream on, while this will causes serious - * issues as buffers already get invalid after - * previous stream off. - * - * No way to flush buffers but to reset the whole css - */ - dev_warn(isp->dev, "Reset CSS to clean up css buffers.\n"); - atomisp_css_flush(isp); - } - return videobuf_streamoff(&pipe->capq); + goto out_unlock; } - if (!pipe->capq.streaming) - return 0; - if (asd->streaming == ATOMISP_DEVICE_STREAMING_ENABLED) first_streamoff = true; @@ -1806,12 +1516,8 @@ int atomisp_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) asd->streaming = ATOMISP_DEVICE_STREAMING_STOPPING; spin_unlock_irqrestore(&isp->lock, flags); - if (!first_streamoff) { - ret = videobuf_streamoff(&pipe->capq); - if (ret) - return ret; + if (!first_streamoff) goto stopsensor; - } atomisp_clear_css_buffer_counters(asd); atomisp_css_irq_enable(isp, IA_CSS_IRQ_INFO_CSS_RECEIVER_SOF, false); @@ -1824,48 +1530,12 @@ int atomisp_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) css_pipe_id = atomisp_get_css_pipe_id(asd); atomisp_css_stop(asd, css_pipe_id, false); - /* cancel work queue*/ - if (asd->video_out_capture.users) { - capture_pipe = &asd->video_out_capture; - wake_up_interruptible(&capture_pipe->capq.wait); - } - if (asd->video_out_vf.users) { - vf_pipe = &asd->video_out_vf; - wake_up_interruptible(&vf_pipe->capq.wait); - } - if (asd->video_out_preview.users) { - preview_pipe = &asd->video_out_preview; - wake_up_interruptible(&preview_pipe->capq.wait); - } - if (asd->video_out_video_capture.users) { - video_pipe = &asd->video_out_video_capture; - wake_up_interruptible(&video_pipe->capq.wait); - } - ret = videobuf_streamoff(&pipe->capq); - if (ret) - return ret; - - /* cleanup css here */ - /* no need for this, as ISP will be reset anyway */ - /*atomisp_flush_bufs_in_css(isp);*/ - - spin_lock_irqsave(&pipe->irq_lock, flags); - list_for_each_entry_safe(vb, _vb, &pipe->activeq, queue) { - vb->state = VIDEOBUF_PREPARED; - list_del(&vb->queue); - } - list_for_each_entry_safe(vb, _vb, &pipe->buffers_waiting_for_param, queue) { - vb->state = VIDEOBUF_PREPARED; - list_del(&vb->queue); - pipe->frame_request_config_id[vb->i] = 0; - } - spin_unlock_irqrestore(&pipe->irq_lock, flags); + atomisp_flush_video_pipe(pipe, true); atomisp_subdev_cleanup_pending_events(asd); stopsensor: - if (atomisp_subdev_streaming_count(asd) + 1 - != atomisp_sensor_start_stream(asd)) - return 0; + if (atomisp_subdev_streaming_count(asd) != atomisp_sensor_start_stream(asd)) + goto out_unlock; ret = v4l2_subdev_call(isp->inputs[asd->input_curr].camera, video, s_stream, 0); @@ -1878,7 +1548,7 @@ stopsensor: /* if other streams are running, isp should not be powered off */ if (atomisp_streaming_count(isp)) { atomisp_css_flush(isp); - return 0; + goto out_unlock; } /* Disable the CSI interface on ANN B0/K0 */ @@ -1894,50 +1564,45 @@ stopsensor: * ISP work around, need to reset isp * Is it correct time to reset ISP when first node does streamoff? */ - if (isp->sw_contex.power_state == ATOM_ISP_POWER_UP) { - unsigned int i; - bool recreate_streams[MAX_STREAM_NUM] = {0}; - - if (isp->isp_timeout) - dev_err(isp->dev, "%s: Resetting with WA activated", - __func__); - /* - * It is possible that the other asd stream is in the stage - * that v4l2_setfmt is just get called on it, which will - * create css stream on that stream. But at this point, there - * is no way to destroy the css stream created on that stream. - * - * So force stream destroy here. - */ - for (i = 0; i < isp->num_of_streams; i++) { - if (isp->asd[i].stream_prepared) { - atomisp_destroy_pipes_stream_force(&isp-> - asd[i]); - recreate_streams[i] = true; - } + if (isp->isp_timeout) + dev_err(isp->dev, "%s: Resetting with WA activated", + __func__); + /* + * It is possible that the other asd stream is in the stage + * that v4l2_setfmt is just get called on it, which will + * create css stream on that stream. But at this point, there + * is no way to destroy the css stream created on that stream. + * + * So force stream destroy here. + */ + for (i = 0; i < isp->num_of_streams; i++) { + if (isp->asd[i].stream_prepared) { + atomisp_destroy_pipes_stream_force(&isp->asd[i]); + recreate_streams[i] = true; } + } - /* disable PUNIT/ISP acknowlede/handshake - SRSE=3 */ - pci_write_config_dword(pdev, PCI_I_CONTROL, - isp->saved_regs.i_control | MRFLD_PCI_I_CONTROL_SRSE_RESET_MASK); - dev_err(isp->dev, "atomisp_reset"); - atomisp_reset(isp); - for (i = 0; i < isp->num_of_streams; i++) { - if (recreate_streams[i]) { - int ret2; - - ret2 = atomisp_create_pipes_stream(&isp->asd[i]); - if (ret2) { - dev_err(isp->dev, "%s error re-creating streams: %d\n", - __func__, ret2); - if (!ret) - ret = ret2; - } + /* disable PUNIT/ISP acknowlede/handshake - SRSE=3 */ + pci_write_config_dword(pdev, PCI_I_CONTROL, + isp->saved_regs.i_control | MRFLD_PCI_I_CONTROL_SRSE_RESET_MASK); + dev_err(isp->dev, "atomisp_reset"); + atomisp_reset(isp); + for (i = 0; i < isp->num_of_streams; i++) { + if (recreate_streams[i]) { + int ret2; + + ret2 = atomisp_create_pipes_stream(&isp->asd[i]); + if (ret2) { + dev_err(isp->dev, "%s error re-creating streams: %d\n", + __func__, ret2); + if (!ret) + ret = ret2; } } - isp->isp_timeout = false; } - return ret; + isp->isp_timeout = false; +out_unlock: + mutex_unlock(&isp->mutex); } /* @@ -2725,13 +2390,13 @@ const struct v4l2_ioctl_ops atomisp_ioctl_ops = { .vidioc_enum_fmt_vid_cap = atomisp_enum_fmt_cap, .vidioc_try_fmt_vid_cap = atomisp_try_fmt_cap, .vidioc_g_fmt_vid_cap = atomisp_g_fmt_cap, - .vidioc_s_fmt_vid_cap = atomisp_set_fmt, - .vidioc_reqbufs = atomisp_reqbufs, - .vidioc_querybuf = atomisp_querybuf, - .vidioc_qbuf = atomisp_qbuf, - .vidioc_dqbuf = atomisp_dqbuf, - .vidioc_streamon = atomisp_streamon, - .vidioc_streamoff = atomisp_streamoff, + .vidioc_s_fmt_vid_cap = atomisp_s_fmt_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = atomisp_qbuf_wrapper, + .vidioc_dqbuf = atomisp_dqbuf_wrapper, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, .vidioc_default = atomisp_vidioc_default, .vidioc_s_parm = atomisp_s_parm, .vidioc_g_parm = atomisp_g_parm, diff --git a/drivers/staging/media/atomisp/pci/atomisp_ioctl.h b/drivers/staging/media/atomisp/pci/atomisp_ioctl.h index c660f631d371..59e071f035f9 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_ioctl.h +++ b/drivers/staging/media/atomisp/pci/atomisp_ioctl.h @@ -39,14 +39,12 @@ int atomisp_pipe_check(struct atomisp_video_pipe *pipe, bool streaming_ok); int atomisp_alloc_css_stat_bufs(struct atomisp_sub_device *asd, uint16_t stream_id); -int atomisp_streamoff(struct file *file, void *fh, enum v4l2_buf_type type); -int atomisp_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *req); +int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count); +void atomisp_stop_streaming(struct vb2_queue *vq); enum ia_css_pipe_id atomisp_get_css_pipe_id(struct atomisp_sub_device *asd); -void atomisp_videobuf_free_buf(struct videobuf_buffer *vb); - extern const struct v4l2_ioctl_ops atomisp_ioctl_ops; unsigned int atomisp_streaming_count(struct atomisp_device *isp); @@ -57,4 +55,8 @@ long atomisp_compat_ioctl32(struct file *file, int atomisp_stream_on_master_slave_sensor(struct atomisp_device *isp, bool isp_timeout); + +int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count); +void atomisp_stop_streaming(struct vb2_queue *vq); + #endif /* __ATOMISP_IOCTL_H__ */ diff --git a/drivers/staging/media/atomisp/pci/atomisp_subdev.c b/drivers/staging/media/atomisp/pci/atomisp_subdev.c index 847dfee6ad78..cadc468b4c2f 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_subdev.c +++ b/drivers/staging/media/atomisp/pci/atomisp_subdev.c @@ -1064,6 +1064,8 @@ static void atomisp_init_subdev_pipe(struct atomisp_sub_device *asd, pipe->asd = asd; pipe->isp = asd->isp; spin_lock_init(&pipe->irq_lock); + mutex_init(&pipe->vb_queue_mutex); + INIT_LIST_HEAD(&pipe->buffers_in_css); INIT_LIST_HEAD(&pipe->activeq); INIT_LIST_HEAD(&pipe->buffers_waiting_for_param); INIT_LIST_HEAD(&pipe->per_frame_params); diff --git a/drivers/staging/media/atomisp/pci/atomisp_subdev.h b/drivers/staging/media/atomisp/pci/atomisp_subdev.h index a1f4da35235d..bd2872cbb50c 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_subdev.h +++ b/drivers/staging/media/atomisp/pci/atomisp_subdev.h @@ -21,8 +21,7 @@ #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> -#include <media/videobuf-core.h> - +#include <media/videobuf2-v4l2.h> #include "atomisp_common.h" #include "atomisp_compat.h" #include "atomisp_v4l2.h" @@ -69,7 +68,12 @@ struct atomisp_video_pipe { struct video_device vdev; enum v4l2_buf_type type; struct media_pad pad; - struct videobuf_queue capq; + struct vb2_queue vb_queue; + /* Lock for vb_queue, when also taking isp->mutex this must be taken first! */ + struct mutex vb_queue_mutex; + /* List of video-buffers handed over to the CSS */ + struct list_head buffers_in_css; + /* List of video-buffers handed over to the driver, but not yet to the CSS */ struct list_head activeq; /* * the buffers waiting for per-frame parameters, this is only valid @@ -79,10 +83,13 @@ struct atomisp_video_pipe { /* the link list to store per_frame parameters */ struct list_head per_frame_params; + /* Filled through atomisp_get_css_frame_info() on queue setup */ + struct ia_css_frame_info frame_info; + /* Store here the initial run mode */ unsigned int default_run_mode; - - unsigned int buffers_in_css; + /* Set from streamoff to disallow queuing further buffers in CSS */ + bool stopping; /* * irq_lock is used to protect video buffer state change operations and @@ -110,6 +117,11 @@ struct atomisp_video_pipe { struct atomisp_css_params_with_list *frame_params[VIDEO_MAX_FRAME]; }; +#define vq_to_pipe(queue) \ + container_of(queue, struct atomisp_video_pipe, vb_queue) + +#define vb_to_pipe(vb) vq_to_pipe((vb)->vb2_queue) + struct atomisp_pad_format { struct v4l2_mbus_framefmt fmt; struct v4l2_rect crop; diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c index d5bb9906ca6f..e786b81921da 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c @@ -573,11 +573,7 @@ static int atomisp_mrfld_pre_power_down(struct atomisp_device *isp) unsigned long flags; spin_lock_irqsave(&isp->lock, flags); - if (isp->sw_contex.power_state == ATOM_ISP_POWER_DOWN) { - spin_unlock_irqrestore(&isp->lock, flags); - dev_dbg(isp->dev, "<%s %d.\n", __func__, __LINE__); - return 0; - } + /* * MRFLD HAS requirement: cannot power off i-unit if * ISP has IRQ not serviced. @@ -724,62 +720,51 @@ static int atomisp_mrfld_power(struct atomisp_device *isp, bool enable) return -EBUSY; } -/* Workaround for pmu_nc_set_power_state not ready in MRFLD */ -int atomisp_mrfld_power_down(struct atomisp_device *isp) -{ - return atomisp_mrfld_power(isp, false); -} - -/* Workaround for pmu_nc_set_power_state not ready in MRFLD */ -int atomisp_mrfld_power_up(struct atomisp_device *isp) -{ - return atomisp_mrfld_power(isp, true); -} - -int atomisp_runtime_suspend(struct device *dev) +int atomisp_power_off(struct device *dev) { - struct atomisp_device *isp = (struct atomisp_device *) - dev_get_drvdata(dev); + struct atomisp_device *isp = dev_get_drvdata(dev); + struct pci_dev *pdev = to_pci_dev(dev); int ret; + u32 reg; + + atomisp_css_uninit(isp); ret = atomisp_mrfld_pre_power_down(isp); if (ret) return ret; - /*Turn off the ISP d-phy*/ - ret = atomisp_ospm_dphy_down(isp); - if (ret) - return ret; + /* + * MRFLD IUNIT DPHY is located in an always-power-on island + * MRFLD HW design need all CSI ports are disabled before + * powering down the IUNIT. + */ + pci_read_config_dword(pdev, MRFLD_PCI_CSI_CONTROL, ®); + reg |= MRFLD_ALL_CSI_PORTS_OFF_MASK; + pci_write_config_dword(pdev, MRFLD_PCI_CSI_CONTROL, reg); + cpu_latency_qos_update_request(&isp->pm_qos, PM_QOS_DEFAULT_VALUE); - return atomisp_mrfld_power_down(isp); + return atomisp_mrfld_power(isp, false); } -int atomisp_runtime_resume(struct device *dev) +int atomisp_power_on(struct device *dev) { struct atomisp_device *isp = (struct atomisp_device *) dev_get_drvdata(dev); int ret; - ret = atomisp_mrfld_power_up(isp); + ret = atomisp_mrfld_power(isp, true); if (ret) return ret; cpu_latency_qos_update_request(&isp->pm_qos, isp->max_isr_latency); - if (isp->sw_contex.power_state == ATOM_ISP_POWER_DOWN) { - /*Turn on ISP d-phy */ - ret = atomisp_ospm_dphy_up(isp); - if (ret) { - dev_err(isp->dev, "Failed to power up ISP!.\n"); - return -EINVAL; - } - } /*restore register values for iUnit and iUnitPHY registers*/ if (isp->saved_regs.pcicmdsts) atomisp_restore_iunit_reg(isp); atomisp_freq_scaling(isp, ATOMISP_DFS_MODE_LOW, true); - return 0; + + return atomisp_css_init(isp); } static int __maybe_unused atomisp_suspend(struct device *dev) @@ -789,7 +774,6 @@ static int __maybe_unused atomisp_suspend(struct device *dev) /* FIXME: only has one isp_subdev at present */ struct atomisp_sub_device *asd = &isp->asd[0]; unsigned long flags; - int ret; /* * FIXME: Suspend is not supported by sensors. Abort if any video @@ -806,45 +790,12 @@ static int __maybe_unused atomisp_suspend(struct device *dev) } spin_unlock_irqrestore(&isp->lock, flags); - ret = atomisp_mrfld_pre_power_down(isp); - if (ret) - return ret; - - /*Turn off the ISP d-phy */ - ret = atomisp_ospm_dphy_down(isp); - if (ret) { - dev_err(isp->dev, "fail to power off ISP\n"); - return ret; - } - cpu_latency_qos_update_request(&isp->pm_qos, PM_QOS_DEFAULT_VALUE); - return atomisp_mrfld_power_down(isp); + return atomisp_power_off(dev); } static int __maybe_unused atomisp_resume(struct device *dev) { - struct atomisp_device *isp = (struct atomisp_device *) - dev_get_drvdata(dev); - int ret; - - ret = atomisp_mrfld_power_up(isp); - if (ret) - return ret; - - cpu_latency_qos_update_request(&isp->pm_qos, isp->max_isr_latency); - - /*Turn on ISP d-phy */ - ret = atomisp_ospm_dphy_up(isp); - if (ret) { - dev_err(isp->dev, "Failed to power up ISP!.\n"); - return -EINVAL; - } - - /*restore register values for iUnit and iUnitPHY registers*/ - if (isp->saved_regs.pcicmdsts) - atomisp_restore_iunit_reg(isp); - - atomisp_freq_scaling(isp, ATOMISP_DFS_MODE_LOW, true); - return 0; + return atomisp_power_on(dev); } int atomisp_csi_lane_config(struct atomisp_device *isp) @@ -1459,7 +1410,6 @@ static int atomisp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i isp->dev = &pdev->dev; isp->base = pcim_iomap_table(pdev)[ATOM_ISP_PCI_BAR]; - isp->sw_contex.power_state = ATOM_ISP_POWER_UP; isp->saved_regs.ispmmadr = start; dev_dbg(&pdev->dev, "atomisp mmio base: %p\n", isp->base); @@ -1723,10 +1673,8 @@ load_fw_fail: atomisp_msi_irq_uninit(isp); - atomisp_ospm_dphy_down(isp); - /* Address later when we worry about the ...field chips */ - if (IS_ENABLED(CONFIG_PM) && atomisp_mrfld_power_down(isp)) + if (IS_ENABLED(CONFIG_PM) && atomisp_mrfld_power(isp, false)) dev_err(&pdev->dev, "Failed to switch off ISP\n"); atomisp_dev_alloc_fail: @@ -1774,8 +1722,8 @@ static const struct pci_device_id atomisp_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, atomisp_pci_tbl); static const struct dev_pm_ops atomisp_pm_ops = { - .runtime_suspend = atomisp_runtime_suspend, - .runtime_resume = atomisp_runtime_resume, + .runtime_suspend = atomisp_power_off, + .runtime_resume = atomisp_power_on, .suspend = atomisp_suspend, .resume = atomisp_resume, }; diff --git a/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_binarydesc.h b/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_binarydesc.h index 965cfda50707..e42eeaeb3ee4 100644 --- a/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_binarydesc.h +++ b/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_binarydesc.h @@ -16,6 +16,8 @@ #ifndef __IA_CSS_PIPE_BINARYDESC_H__ #define __IA_CSS_PIPE_BINARYDESC_H__ +#include <linux/math.h> + #include <ia_css_types.h> /* ia_css_pipe */ #include <ia_css_frame_public.h> /* ia_css_frame_info */ #include <ia_css_binary.h> /* ia_css_binary_descr */ @@ -56,17 +58,12 @@ void ia_css_pipe_get_vfpp_binarydesc( * * @param[in] bds_factor: The bayer downscaling factor. * (= The bds_factor member in the sh_css_bds_factor structure.) - * @param[out] bds_factor_numerator: The numerator of the bayer downscaling factor. - * (= The numerator member in the sh_css_bds_factor structure.) - * @param[out] bds_factor_denominator: The denominator of the bayer downscaling factor. - * (= The denominator member in the sh_css_bds_factor structure.) + * @param[out] bds: The rational fraction of the bayer downscaling factor. + * (= The respective member in the sh_css_bds_factor structure.) * @return 0 or error code upon error. * */ -int sh_css_bds_factor_get_numerator_denominator( - unsigned int bds_factor, - unsigned int *bds_factor_numerator, - unsigned int *bds_factor_denominator); +int sh_css_bds_factor_get_fract(unsigned int bds_factor, struct u32_fract *bds); /* @brief Get a binary descriptor for preview stage. * diff --git a/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_stagedesc.h b/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_stagedesc.h index 40c8145a0797..7a0c988d89ee 100644 --- a/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_stagedesc.h +++ b/drivers/staging/media/atomisp/pci/camera/pipe/interface/ia_css_pipe_stagedesc.h @@ -38,11 +38,6 @@ void ia_css_pipe_get_firmwares_stage_desc( const struct ia_css_fw_info *fw, unsigned int mode); -void ia_css_pipe_get_acc_stage_desc( - struct ia_css_pipeline_stage_desc *stage_desc, - struct ia_css_binary *binary, - struct ia_css_fw_info *fw); - void ia_css_pipe_get_sp_func_stage_desc( struct ia_css_pipeline_stage_desc *stage_desc, struct ia_css_frame *out_frame, diff --git a/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_binarydesc.c b/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_binarydesc.c index 7dd0e4a53c8b..06664ce75b60 100644 --- a/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_binarydesc.c +++ b/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_binarydesc.c @@ -13,6 +13,9 @@ * more details. */ +#include <linux/kernel.h> +#include <linux/math.h> + #include "ia_css_pipe_binarydesc.h" #include "ia_css_frame_format.h" #include "ia_css_pipe.h" @@ -23,7 +26,6 @@ #include <assert_support.h> /* HRT_GDC_N */ #include "gdc_device.h" -#include <linux/kernel.h> /* This module provides a binary descriptions to used to find a binary. Since, * every stage is associated with a binary, it implicity helps stage @@ -126,40 +128,29 @@ void ia_css_pipe_get_vfpp_binarydesc( IA_CSS_LEAVE_PRIVATE(""); } -static struct sh_css_bds_factor bds_factors_list[] = { - {1, 1, SH_CSS_BDS_FACTOR_1_00}, - {5, 4, SH_CSS_BDS_FACTOR_1_25}, - {3, 2, SH_CSS_BDS_FACTOR_1_50}, - {2, 1, SH_CSS_BDS_FACTOR_2_00}, - {9, 4, SH_CSS_BDS_FACTOR_2_25}, - {5, 2, SH_CSS_BDS_FACTOR_2_50}, - {3, 1, SH_CSS_BDS_FACTOR_3_00}, - {4, 1, SH_CSS_BDS_FACTOR_4_00}, - {9, 2, SH_CSS_BDS_FACTOR_4_50}, - {5, 1, SH_CSS_BDS_FACTOR_5_00}, - {6, 1, SH_CSS_BDS_FACTOR_6_00}, - {8, 1, SH_CSS_BDS_FACTOR_8_00} +static struct u32_fract bds_factors_list[] = { + [SH_CSS_BDS_FACTOR_1_00] = {1, 1}, + [SH_CSS_BDS_FACTOR_1_25] = {5, 4}, + [SH_CSS_BDS_FACTOR_1_50] = {3, 2}, + [SH_CSS_BDS_FACTOR_2_00] = {2, 1}, + [SH_CSS_BDS_FACTOR_2_25] = {9, 4}, + [SH_CSS_BDS_FACTOR_2_50] = {5, 2}, + [SH_CSS_BDS_FACTOR_3_00] = {3, 1}, + [SH_CSS_BDS_FACTOR_4_00] = {4, 1}, + [SH_CSS_BDS_FACTOR_4_50] = {9, 2}, + [SH_CSS_BDS_FACTOR_5_00] = {5, 1}, + [SH_CSS_BDS_FACTOR_6_00] = {6, 1}, + [SH_CSS_BDS_FACTOR_8_00] = {8, 1}, }; -int sh_css_bds_factor_get_numerator_denominator( - unsigned int bds_factor, - unsigned int *bds_factor_numerator, - unsigned int *bds_factor_denominator) +int sh_css_bds_factor_get_fract(unsigned int bds_factor, struct u32_fract *bds) { - unsigned int i; - - /* Loop over all bds factors until a match is found */ - for (i = 0; i < ARRAY_SIZE(bds_factors_list); i++) { - if (bds_factors_list[i].bds_factor == bds_factor) { - *bds_factor_numerator = bds_factors_list[i].numerator; - *bds_factor_denominator = bds_factors_list[i].denominator; - return 0; - } - } + /* Throw an error since bds_factor cannot be found in bds_factors_list */ + if (bds_factor >= ARRAY_SIZE(bds_factors_list)) + return -EINVAL; - /* Throw an error since bds_factor cannot be found - in bds_factors_list */ - return -EINVAL; + *bds = bds_factors_list[bds_factor]; + return 0; } int binarydesc_calculate_bds_factor( @@ -194,7 +185,7 @@ int binarydesc_calculate_bds_factor( (out_h * num / den <= in_h); if (cond) { - *bds_factor = bds_factors_list[i].bds_factor; + *bds_factor = i; return 0; } } diff --git a/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_stagedesc.c b/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_stagedesc.c index 82a24aabe8ce..6c93fa1c683b 100644 --- a/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_stagedesc.c +++ b/drivers/staging/media/atomisp/pci/camera/pipe/src/pipe_stagedesc.c @@ -74,27 +74,6 @@ void ia_css_pipe_get_firmwares_stage_desc( stage_desc->vf_frame = vf_frame; } -void ia_css_pipe_get_acc_stage_desc( - struct ia_css_pipeline_stage_desc *stage_desc, - struct ia_css_binary *binary, - struct ia_css_fw_info *fw) -{ - unsigned int i; - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE, - "ia_css_pipe_get_acc_stage_desc() enter:\n"); - stage_desc->binary = binary; - stage_desc->firmware = fw; - stage_desc->sp_func = IA_CSS_PIPELINE_NO_FUNC; - stage_desc->max_input_width = 0; - stage_desc->mode = IA_CSS_BINARY_MODE_VF_PP; - stage_desc->in_frame = NULL; - for (i = 0; i < IA_CSS_BINARY_MAX_OUTPUT_PORTS; i++) { - stage_desc->out_frame[i] = NULL; - } - stage_desc->vf_frame = NULL; -} - void ia_css_pipe_get_sp_func_stage_desc( struct ia_css_pipeline_stage_desc *stage_desc, struct ia_css_frame *out_frame, diff --git a/drivers/staging/media/atomisp/pci/css_2401_system/host/pixelgen_private.h b/drivers/staging/media/atomisp/pci/css_2401_system/host/pixelgen_private.h index 1c7938d8ccb5..8f79424bedb2 100644 --- a/drivers/staging/media/atomisp/pci/css_2401_system/host/pixelgen_private.h +++ b/drivers/staging/media/atomisp/pci/css_2401_system/host/pixelgen_private.h @@ -161,7 +161,7 @@ STORAGE_CLASS_PIXELGEN_C void pixelgen_ctrl_dump_state( state->syng_stat_fcnt); ia_css_print("Pixel Generator ID %d syng stat done 0x%x\n", ID, state->syng_stat_done); - ia_css_print("Pixel Generator ID %d tpg modee 0x%x\n", ID, state->tpg_mode); + ia_css_print("Pixel Generator ID %d tpg mode 0x%x\n", ID, state->tpg_mode); ia_css_print("Pixel Generator ID %d tpg hcnt mask 0x%x\n", ID, state->tpg_hcnt_mask); ia_css_print("Pixel Generator ID %d tpg hcnt mask 0x%x\n", ID, diff --git a/drivers/staging/media/atomisp/pci/hmm/hmm.c b/drivers/staging/media/atomisp/pci/hmm/hmm.c index fc6cfe9f7744..bb12644fd033 100644 --- a/drivers/staging/media/atomisp/pci/hmm/hmm.c +++ b/drivers/staging/media/atomisp/pci/hmm/hmm.c @@ -41,11 +41,9 @@ static bool hmm_initialized; /* * p: private - * s: shared - * u: user - * i: ion + * v: vmalloc */ -static const char hmm_bo_type_string[] = "psui"; +static const char hmm_bo_type_string[] = "pv"; static ssize_t bo_show(struct device *dev, struct device_attribute *attr, char *buf, struct list_head *bo_list, bool active) @@ -168,7 +166,8 @@ void hmm_cleanup(void) hmm_initialized = false; } -static ia_css_ptr __hmm_alloc(size_t bytes, enum hmm_bo_type type, const void __user *userptr) +static ia_css_ptr __hmm_alloc(size_t bytes, enum hmm_bo_type type, + void *vmalloc_addr) { unsigned int pgnr; struct hmm_buffer_object *bo; @@ -192,7 +191,7 @@ static ia_css_ptr __hmm_alloc(size_t bytes, enum hmm_bo_type type, const void __ } /* Allocate pages for memory */ - ret = hmm_bo_alloc_pages(bo, type, userptr); + ret = hmm_bo_alloc_pages(bo, type, vmalloc_addr); if (ret) { dev_err(atomisp_dev, "hmm_bo_alloc_pages failed.\n"); goto alloc_page_err; @@ -205,9 +204,8 @@ static ia_css_ptr __hmm_alloc(size_t bytes, enum hmm_bo_type type, const void __ goto bind_err; } - dev_dbg(atomisp_dev, - "%s: pages: 0x%08x (%zu bytes), type: %d, user ptr %p\n", - __func__, bo->start, bytes, type, userptr); + dev_dbg(atomisp_dev, "pages: 0x%08x (%zu bytes), type: %d, vmalloc %p\n", + bo->start, bytes, type, vmalloc); return bo->start; @@ -224,9 +222,9 @@ ia_css_ptr hmm_alloc(size_t bytes) return __hmm_alloc(bytes, HMM_BO_PRIVATE, NULL); } -ia_css_ptr hmm_create_from_userdata(size_t bytes, const void __user *userptr) +ia_css_ptr hmm_create_from_vmalloc_buf(size_t bytes, void *vmalloc_addr) { - return __hmm_alloc(bytes, HMM_BO_USER, userptr); + return __hmm_alloc(bytes, HMM_BO_VMALLOC, vmalloc_addr); } void hmm_free(ia_css_ptr virt) diff --git a/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c b/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c index a5fd6d38d3c4..5e53eed8ae95 100644 --- a/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c +++ b/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c @@ -638,6 +638,7 @@ static int alloc_private_pages(struct hmm_buffer_object *bo) ret = alloc_pages_bulk_array(gfp, bo->pgnr, bo->pages); if (ret != bo->pgnr) { free_pages_bulk_array(ret, bo->pages); + dev_err(atomisp_dev, "alloc_pages_bulk_array() failed\n"); return -ENOMEM; } @@ -651,61 +652,34 @@ static int alloc_private_pages(struct hmm_buffer_object *bo) return 0; } -static void free_user_pages(struct hmm_buffer_object *bo, - unsigned int page_nr) +static int alloc_vmalloc_pages(struct hmm_buffer_object *bo, void *vmalloc_addr) { + void *vaddr = vmalloc_addr; int i; - for (i = 0; i < page_nr; i++) - put_page(bo->pages[i]); -} - -/* - * Convert user space virtual address into pages list - */ -static int alloc_user_pages(struct hmm_buffer_object *bo, - const void __user *userptr) -{ - int page_nr; - - userptr = untagged_addr(userptr); - - /* Handle frame buffer allocated in user space */ - mutex_unlock(&bo->mutex); - page_nr = get_user_pages_fast((unsigned long)userptr, bo->pgnr, 1, bo->pages); - mutex_lock(&bo->mutex); - - /* can be written by caller, not forced */ - if (page_nr != bo->pgnr) { - dev_err(atomisp_dev, - "get_user_pages err: bo->pgnr = %d, pgnr actually pinned = %d.\n", - bo->pgnr, page_nr); - if (page_nr < 0) - page_nr = 0; - goto out_of_mem; + for (i = 0; i < bo->pgnr; i++) { + bo->pages[i] = vmalloc_to_page(vaddr); + if (!bo->pages[i]) { + dev_err(atomisp_dev, "Error could not get page %d of vmalloc buf\n", i); + return -ENOMEM; + } + vaddr += PAGE_SIZE; } return 0; - -out_of_mem: - - free_user_pages(bo, page_nr); - - return -ENOMEM; } /* * allocate/free physical pages for the bo. * * type indicate where are the pages from. currently we have 3 types - * of memory: HMM_BO_PRIVATE, HMM_BO_USER. + * of memory: HMM_BO_PRIVATE, HMM_BO_VMALLOC. * - * userptr is only valid when type is HMM_BO_USER, it indicates - * the start address from user space task. + * vmalloc_addr is only valid when type is HMM_BO_VMALLOC. */ int hmm_bo_alloc_pages(struct hmm_buffer_object *bo, enum hmm_bo_type type, - const void __user *userptr) + void *vmalloc_addr) { int ret = -EINVAL; @@ -720,14 +694,10 @@ int hmm_bo_alloc_pages(struct hmm_buffer_object *bo, goto alloc_err; } - /* - * TO DO: - * add HMM_BO_USER type - */ if (type == HMM_BO_PRIVATE) { ret = alloc_private_pages(bo); - } else if (type == HMM_BO_USER) { - ret = alloc_user_pages(bo, userptr); + } else if (type == HMM_BO_VMALLOC) { + ret = alloc_vmalloc_pages(bo, vmalloc_addr); } else { dev_err(atomisp_dev, "invalid buffer type.\n"); ret = -EINVAL; @@ -771,8 +741,8 @@ void hmm_bo_free_pages(struct hmm_buffer_object *bo) if (bo->type == HMM_BO_PRIVATE) free_private_bo_pages(bo); - else if (bo->type == HMM_BO_USER) - free_user_pages(bo, bo->pgnr); + else if (bo->type == HMM_BO_VMALLOC) + ; /* No-op, nothing to do */ else dev_err(atomisp_dev, "invalid buffer type.\n"); diff --git a/drivers/staging/media/atomisp/pci/ia_css_frame_public.h b/drivers/staging/media/atomisp/pci/ia_css_frame_public.h index 514d933f934d..7ba464abf447 100644 --- a/drivers/staging/media/atomisp/pci/ia_css_frame_public.h +++ b/drivers/staging/media/atomisp/pci/ia_css_frame_public.h @@ -20,6 +20,7 @@ * This file contains structs to describe various frame-formats supported by the ISP. */ +#include <media/videobuf2-v4l2.h> #include <type_support.h> #include "ia_css_err.h" #include "ia_css_types.h" @@ -146,7 +147,18 @@ enum ia_css_frame_flash_state { * This is the main structure used for all input and output images. */ struct ia_css_frame { - struct ia_css_frame_info info; /** info struct describing the frame */ + /* + * The videobuf2 core will allocate buffers including room for private + * data (the rest of struct ia_css_frame). The vb2_v4l2_buffer must + * be the first member for this to work! + * Note the atomisp code also uses ia_css_frame-s which are not used + * as v4l2-buffers in some places. In this case the vb2 member will + * be unused. + */ + struct vb2_v4l2_buffer vb; + /* List-head for linking into the activeq or buffers_waiting_for_param list */ + struct list_head queue; + struct ia_css_frame_info frame_info; /** info struct describing the frame */ ia_css_ptr data; /** pointer to start of image data */ unsigned int data_bytes; /** size of image data in bytes */ /* LA: move this to ia_css_buffer */ @@ -183,22 +195,16 @@ struct ia_css_frame { info.format */ }; +#define vb_to_frame(vb2) \ + container_of(to_vb2_v4l2_buffer(vb2), struct ia_css_frame, vb) + #define DEFAULT_FRAME { \ - .info = IA_CSS_BINARY_DEFAULT_FRAME_INFO, \ + .frame_info = IA_CSS_BINARY_DEFAULT_FRAME_INFO, \ .dynamic_queue_id = SH_CSS_INVALID_QUEUE_ID, \ .buf_type = IA_CSS_BUFFER_TYPE_INVALID, \ .flash_state = IA_CSS_FRAME_FLASH_STATE_NONE, \ } -/* @brief Fill a frame with zeros - * - * @param frame The frame. - * @return None - * - * Fill a frame with pixel values of zero - */ -void ia_css_frame_zero(struct ia_css_frame *frame); - /* @brief Allocate a CSS frame structure * * @param frame The allocated frame. @@ -220,6 +226,17 @@ ia_css_frame_allocate(struct ia_css_frame **frame, unsigned int stride, unsigned int raw_bit_depth); +/* @brief Initialize a CSS frame structure using a frame info structure. + * + * @param frame The allocated frame. + * @param[in] info The frame info structure. + * @return The error code. + * + * Initialize a frame using the resolution and format from a frame info struct. + */ +int ia_css_frame_init_from_info(struct ia_css_frame *frame, + const struct ia_css_frame_info *info); + /* @brief Allocate a CSS frame structure using a frame info structure. * * @param frame The allocated frame. @@ -244,69 +261,10 @@ ia_css_frame_allocate_from_info(struct ia_css_frame **frame, void ia_css_frame_free(struct ia_css_frame *frame); -/* @brief Allocate a CSS frame structure using a frame info structure. - * - * @param frame The allocated frame. - * @param[in] info The frame info structure. - * @return The error code. - * - * Allocate an empty CSS frame with no data buffer using the parameters - * in the frame info. - */ -int -ia_css_frame_create_from_info(struct ia_css_frame **frame, - const struct ia_css_frame_info *info); - -/* @brief Set a mapped data buffer to a CSS frame - * - * @param[in] frame Valid CSS frame pointer - * @param[in] mapped_data Mapped data buffer to be assigned to the CSS frame - * @param[in] data_size_bytes Size of the mapped_data in bytes - * @return The error code. - * - * Sets a mapped data buffer to this frame. This function can be called multiple - * times with different buffers or NULL to reset the data pointer. This API - * would not try free the mapped_data and its the callers responsiblity to - * free the mapped_data buffer. However if ia_css_frame_free() is called and - * the frame had a valid data buffer, it would be freed along with the frame. - */ -int -ia_css_frame_set_data(struct ia_css_frame *frame, - const ia_css_ptr mapped_data, - size_t data_size_bytes); - -/* @brief Map an existing frame data pointer to a CSS frame. - * - * @param frame Pointer to the frame to be initialized - * @param[in] info The frame info. - * @param[in] data Pointer to the allocated frame data. - * @param[in] attribute Attributes to be passed to mmgr_mmap. - * @param[in] context Pointer to the a context to be passed to mmgr_mmap. - * @return The allocated frame structure. - * - * This function maps a pre-allocated pointer into a CSS frame. This can be - * used when an upper software layer is responsible for allocating the frame - * data and it wants to share that frame pointer with the CSS code. - * This function will fill the CSS frame structure just like - * ia_css_frame_allocate() does, but instead of allocating the memory, it will - * map the pre-allocated memory into the CSS address space. - */ -int -ia_css_frame_map(struct ia_css_frame **frame, - const struct ia_css_frame_info *info, - const void __user *data, - unsigned int pgnr); - -/* @brief Unmap a CSS frame structure. - * - * @param[in] frame Pointer to the CSS frame. - * @return None - * - * This function unmaps the frame data pointer within a CSS frame and - * then frees the CSS frame structure. Use this for frame pointers created - * using ia_css_frame_map(). - */ -void -ia_css_frame_unmap(struct ia_css_frame *frame); +static inline const struct ia_css_frame_info * +ia_css_frame_get_info(const struct ia_css_frame *frame) +{ + return frame ? &frame->frame_info : NULL; +} #endif /* __IA_CSS_FRAME_PUBLIC_H */ diff --git a/drivers/staging/media/atomisp/pci/ia_css_pipe.h b/drivers/staging/media/atomisp/pci/ia_css_pipe.h index fb58535bff40..22522968b9e6 100644 --- a/drivers/staging/media/atomisp/pci/ia_css_pipe.h +++ b/drivers/staging/media/atomisp/pci/ia_css_pipe.h @@ -37,7 +37,6 @@ struct ia_css_preview_settings { struct ia_css_pipe *copy_pipe; struct ia_css_pipe *capture_pipe; - struct ia_css_pipe *acc_pipe; }; #define IA_CSS_DEFAULT_PREVIEW_SETTINGS { \ @@ -156,7 +155,7 @@ struct ia_css_pipe { #define IA_CSS_DEFAULT_PIPE { \ .config = DEFAULT_PIPE_CONFIG, \ .info = DEFAULT_PIPE_INFO, \ - .mode = IA_CSS_PIPE_ID_ACC, /* (pipe_id) */ \ + .mode = IA_CSS_PIPE_ID_VIDEO, /* (pipe_id) */ \ .pipeline = DEFAULT_PIPELINE, \ .output_info = {IA_CSS_BINARY_DEFAULT_FRAME_INFO}, \ .bds_output_info = IA_CSS_BINARY_DEFAULT_FRAME_INFO, \ diff --git a/drivers/staging/media/atomisp/pci/ia_css_pipe_public.h b/drivers/staging/media/atomisp/pci/ia_css_pipe_public.h index 7352cbf779fb..8ac1586dce4e 100644 --- a/drivers/staging/media/atomisp/pci/ia_css_pipe_public.h +++ b/drivers/staging/media/atomisp/pci/ia_css_pipe_public.h @@ -45,7 +45,6 @@ enum ia_css_pipe_mode { IA_CSS_PIPE_MODE_PREVIEW, /** Preview pipe */ IA_CSS_PIPE_MODE_VIDEO, /** Video pipe */ IA_CSS_PIPE_MODE_CAPTURE, /** Still capture pipe */ - IA_CSS_PIPE_MODE_ACC, /** Accelerated pipe */ IA_CSS_PIPE_MODE_COPY, /** Copy pipe, only used for embedded/image data copying */ IA_CSS_PIPE_MODE_YUVPP, /** YUV post processing pipe, used for all use cases with YUV input, for SoC sensor and external ISP */ @@ -95,21 +94,11 @@ struct ia_css_pipe_config { /** output of YUV scaling */ struct ia_css_frame_info vf_output_info[IA_CSS_PIPE_MAX_OUTPUT_STAGE]; /** output of VF YUV scaling */ - struct ia_css_fw_info *acc_extension; - /** Pipeline extension accelerator */ - struct ia_css_fw_info **acc_stages; - /** Standalone accelerator stages */ - u32 num_acc_stages; - /** Number of standalone accelerator stages */ struct ia_css_capture_config default_capture_config; /** Default capture config for initial capture pipe configuration. */ struct ia_css_resolution dvs_envelope; /** temporary */ enum ia_css_frame_delay dvs_frame_delay; /** indicates the DVS loop delay in frame periods */ - int acc_num_execs; - /** For acceleration pipes only: determine how many times the pipe - should be run. Setting this to -1 means it will run until - stopped. */ bool enable_dz; /** Disabling digital zoom for a pipeline, if this is set to false, then setting a zoom factor will have no effect. @@ -153,7 +142,6 @@ struct ia_css_pipe_config { .vf_output_info = {IA_CSS_BINARY_DEFAULT_FRAME_INFO}, \ .default_capture_config = DEFAULT_CAPTURE_CONFIG, \ .dvs_frame_delay = IA_CSS_FRAME_DELAY_1, \ - .acc_num_execs = -1, \ } /* Pipe info, this struct describes properties of a pipe after it's stream has @@ -224,9 +212,6 @@ struct ia_css_pipe_info { {{0, 0}, 0, 0, 0, 0}, // second_output_info {{0, 0}, 0, 0, 0, 0}, // vf_output_info {{0, 0}, 0, 0, 0, 0}, // second_vf_output_info - NULL, // acc_extension - NULL, // acc_stages - 0, // num_acc_stages { IA_CSS_CAPTURE_MODE_RAW, // mode false, // enable_xnr @@ -234,7 +219,6 @@ struct ia_css_pipe_info { }, // default_capture_config {0, 0}, // dvs_envelope 1, // dvs_frame_delay - -1, // acc_num_execs true, // enable_dz NULL, // p_isp_config }; @@ -426,59 +410,6 @@ int ia_css_pipe_dequeue_buffer(struct ia_css_pipe *pipe, struct ia_css_buffer *buffer); -/* @brief Set the state (Enable or Disable) of the Extension stage in the - * given pipe. - * @param[in] pipe Pipe handle. - * @param[in] fw_handle Extension firmware Handle (ia_css_fw_info.handle) - * @param[in] enable Enable Flag (1 to enable ; 0 to disable) - * - * @return - * 0 : Success - * -EINVAL : Invalid Parameters - * -EBUSY : Inactive QOS Pipe - * (No active stream with this pipe) - * - * This function will request state change (enable or disable) for the Extension - * stage (firmware handle) in the given pipe. - * - * Note: - * 1. Extension can be enabled/disabled only on QOS Extensions - * 2. Extension can be enabled/disabled only with an active QOS Pipe - * 3. Initial(Default) state of QOS Extensions is Disabled - * 4. State change cannot be guaranteed immediately OR on frame boundary - * - */ -int -ia_css_pipe_set_qos_ext_state(struct ia_css_pipe *pipe, - u32 fw_handle, - bool enable); - -/* @brief Get the state (Enable or Disable) of the Extension stage in the - * given pipe. - * @param[in] pipe Pipe handle. - * @param[in] fw_handle Extension firmware Handle (ia_css_fw_info.handle) - * @param[out] *enable Enable Flag - * - * @return - * 0 : Success - * -EINVAL : Invalid Parameters - * -EBUSY : Inactive QOS Pipe - * (No active stream with this pipe) - * - * This function will query the state of the Extension stage (firmware handle) - * in the given Pipe. - * - * Note: - * 1. Extension state can be queried only on QOS Extensions - * 2. Extension can be enabled/disabled only with an active QOS Pipe - * 3. Initial(Default) state of QOS Extensions is Disabled. - * - */ -int -ia_css_pipe_get_qos_ext_state(struct ia_css_pipe *pipe, - u32 fw_handle, - bool *enable); - /* @brief Get selected configuration settings * @param[in] pipe The pipe. * @param[out] config Configuration settings. diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/bayer_io_ls/ia_css_bayer_io.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/bayer_io_ls/ia_css_bayer_io.host.c index c7d88552dfde..0091e2a3da52 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/bayer_io_ls/ia_css_bayer_io.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/bayer_io_ls/ia_css_bayer_io.host.c @@ -28,9 +28,7 @@ int ia_css_bayer_io_config(const struct ia_css_binary *binary, const struct ia_css_frame *in_frame = args->in_frame; const struct ia_css_frame **out_frames = (const struct ia_css_frame **) &args->out_frame; - const struct ia_css_frame_info *in_frame_info = (in_frame) ? &in_frame->info : - &binary->in_frame_info; - + const struct ia_css_frame_info *in_frame_info = ia_css_frame_get_info(in_frame); const unsigned int ddr_bits_per_element = sizeof(short) * 8; const unsigned int ddr_elems_per_word = ceil_div(HIVE_ISP_DDR_WORD_BITS, ddr_bits_per_element); @@ -80,12 +78,12 @@ int ia_css_bayer_io_config(const struct ia_css_binary *binary, "ia_css_bayer_io_config() put part enter:\n"); #endif - ret = ia_css_dma_configure_from_info(&config, &out_frames[0]->info); + ret = ia_css_dma_configure_from_info(&config, &out_frames[0]->frame_info); if (ret) return ret; to->base_address = out_frames[0]->data; - to->width = out_frames[0]->info.res.width; - to->height = out_frames[0]->info.res.height; + to->width = out_frames[0]->frame_info.res.width; + to->height = out_frames[0]->frame_info.res.height; to->stride = config.stride; to->ddr_elems_per_word = ddr_elems_per_word; diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/yuv444_io_ls/ia_css_yuv444_io.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/yuv444_io_ls/ia_css_yuv444_io.host.c index 7d2ef6e26ee6..32c504a950ce 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/yuv444_io_ls/ia_css_yuv444_io.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/ipu2_io_ls/yuv444_io_ls/ia_css_yuv444_io.host.c @@ -28,9 +28,7 @@ int ia_css_yuv444_io_config(const struct ia_css_binary *binary, const struct ia_css_frame *in_frame = args->in_frame; const struct ia_css_frame **out_frames = (const struct ia_css_frame **) &args->out_frame; - const struct ia_css_frame_info *in_frame_info = (in_frame) ? &in_frame->info : - &binary->in_frame_info; - + const struct ia_css_frame_info *in_frame_info = ia_css_frame_get_info(in_frame); const unsigned int ddr_bits_per_element = sizeof(short) * 8; const unsigned int ddr_elems_per_word = ceil_div(HIVE_ISP_DDR_WORD_BITS, ddr_bits_per_element); @@ -81,13 +79,13 @@ int ia_css_yuv444_io_config(const struct ia_css_binary *binary, "ia_css_yuv444_io_config() put part enter:\n"); #endif - ret = ia_css_dma_configure_from_info(&config, &out_frames[0]->info); + ret = ia_css_dma_configure_from_info(&config, &out_frames[0]->frame_info); if (ret) return ret; to->base_address = out_frames[0]->data; - to->width = out_frames[0]->info.res.width; - to->height = out_frames[0]->info.res.height; + to->width = out_frames[0]->frame_info.res.width; + to->height = out_frames[0]->frame_info.res.height; to->stride = config.stride; to->ddr_elems_per_word = ddr_elems_per_word; diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/ref/ref_1.0/ia_css_ref.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/ref/ref_1.0/ia_css_ref.host.c index 08ed916a7eb8..9288a7a37b37 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/ref/ref_1.0/ia_css_ref.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/ref/ref_1.0/ia_css_ref.host.c @@ -30,7 +30,7 @@ int ia_css_ref_config(struct sh_css_isp_ref_isp_config *to, int ret; if (from->ref_frames[0]) { - ret = ia_css_dma_configure_from_info(&to->port_b, &from->ref_frames[0]->info); + ret = ia_css_dma_configure_from_info(&to->port_b, &from->ref_frames[0]->frame_info); if (ret) return ret; to->width_a_over_b = elems_a / to->port_b.elems; diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c index 53050c0c49fc..a5fea753ec64 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c @@ -79,11 +79,11 @@ int ia_css_tnr_config(struct sh_css_isp_tnr_isp_config *to, unsigned int i; int ret; - ret = ia_css_dma_configure_from_info(&to->port_b, &from->tnr_frames[0]->info); + ret = ia_css_dma_configure_from_info(&to->port_b, &from->tnr_frames[0]->frame_info); if (ret) return ret; to->width_a_over_b = elems_a / to->port_b.elems; - to->frame_height = from->tnr_frames[0]->info.res.height; + to->frame_height = from->tnr_frames[0]->frame_info.res.height; for (i = 0; i < NUM_VIDEO_TNR_FRAMES; i++) { to->tnr_frame_addr[i] = from->tnr_frames[i]->data + from->tnr_frames[i]->planes.yuyv.offset; diff --git a/drivers/staging/media/atomisp/pci/runtime/binary/src/binary.c b/drivers/staging/media/atomisp/pci/runtime/binary/src/binary.c index 406ed5fb4c6a..768da86b8c2c 100644 --- a/drivers/staging/media/atomisp/pci/runtime/binary/src/binary.c +++ b/drivers/staging/media/atomisp/pci/runtime/binary/src/binary.c @@ -13,6 +13,8 @@ * more details. */ +#include <linux/math.h> + #include <math_support.h> #include <gdc_device.h> /* HR_GDC_N */ @@ -128,16 +130,8 @@ ia_css_binary_compute_shading_table_bayer_origin( { int err; - /* Numerator and denominator of the fixed bayer downscaling factor. - (numerator >= denominator) */ - unsigned int bds_num, bds_den; - - /* Horizontal/Vertical ratio of bayer scaling - between input area and output area. */ - unsigned int bs_hor_ratio_in; - unsigned int bs_hor_ratio_out; - unsigned int bs_ver_ratio_in; - unsigned int bs_ver_ratio_out; + /* Rational fraction of the fixed bayer downscaling factor. */ + struct u32_fract bds; /* Left padding set by InputFormatter. */ unsigned int left_padding_bqs; /* in bqs */ @@ -158,19 +152,11 @@ ia_css_binary_compute_shading_table_bayer_origin( unsigned int bad_bqs_on_top_before_bs; /* in bqs */ unsigned int bad_bqs_on_top_after_bs; /* in bqs */ - /* Get the numerator and denominator of bayer downscaling factor. */ - err = sh_css_bds_factor_get_numerator_denominator - (required_bds_factor, &bds_num, &bds_den); + /* Get the rational fraction of bayer downscaling factor. */ + err = sh_css_bds_factor_get_fract(required_bds_factor, &bds); if (err) return err; - /* Set the horizontal/vertical ratio of bayer scaling - between input area and output area. */ - bs_hor_ratio_in = bds_num; - bs_hor_ratio_out = bds_den; - bs_ver_ratio_in = bds_num; - bs_ver_ratio_out = bds_den; - /* Set the left padding set by InputFormatter. (ifmtr.c) */ if (stream_config->left_padding == -1) left_padding_bqs = _ISP_BQS(binary->left_padding); @@ -228,18 +214,18 @@ ia_css_binary_compute_shading_table_bayer_origin( located on the shading table during the shading correction. */ res->sc_bayer_origin_x_bqs_on_shading_table = ((left_padding_adjusted_bqs + bad_bqs_on_left_before_bs) - * bs_hor_ratio_out + bs_hor_ratio_in / 2) / bs_hor_ratio_in + * bds.denominator + bds.numerator / 2) / bds.numerator + bad_bqs_on_left_after_bs; - /* "+ bs_hor_ratio_in/2": rounding for division by bs_hor_ratio_in */ + /* "+ bds.numerator / 2": rounding for division by bds.numerator */ res->sc_bayer_origin_y_bqs_on_shading_table = - (bad_bqs_on_top_before_bs * bs_ver_ratio_out + bs_ver_ratio_in / 2) / bs_ver_ratio_in + (bad_bqs_on_top_before_bs * bds.denominator + bds.numerator / 2) / bds.numerator + bad_bqs_on_top_after_bs; - /* "+ bs_ver_ratio_in/2": rounding for division by bs_ver_ratio_in */ + /* "+ bds.numerator / 2": rounding for division by bds.numerator */ - res->bayer_scale_hor_ratio_in = (uint32_t)bs_hor_ratio_in; - res->bayer_scale_hor_ratio_out = (uint32_t)bs_hor_ratio_out; - res->bayer_scale_ver_ratio_in = (uint32_t)bs_ver_ratio_in; - res->bayer_scale_ver_ratio_out = (uint32_t)bs_ver_ratio_out; + res->bayer_scale_hor_ratio_in = bds.numerator; + res->bayer_scale_hor_ratio_out = bds.denominator; + res->bayer_scale_ver_ratio_in = bds.numerator; + res->bayer_scale_ver_ratio_out = bds.denominator; return err; } diff --git a/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c b/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c index 3d269bd23207..bb6204cb42c5 100644 --- a/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c +++ b/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c @@ -133,7 +133,6 @@ static const char *const pipe_id_to_str[] = { /* [IA_CSS_PIPE_ID_VIDEO] =*/ "video", /* [IA_CSS_PIPE_ID_CAPTURE] =*/ "capture", /* [IA_CSS_PIPE_ID_YUVPP] =*/ "yuvpp", - /* [IA_CSS_PIPE_ID_ACC] =*/ "accelerator" }; static char dot_id_input_bin[SH_CSS_MAX_BINARY_NAME + 10]; @@ -1301,11 +1300,11 @@ void ia_css_debug_frame_print(const struct ia_css_frame *frame, data = (char *)HOST_ADDRESS(frame->data); ia_css_debug_dtrace(2, "frame %s (%p):\n", descr, frame); ia_css_debug_dtrace(2, " resolution = %dx%d\n", - frame->info.res.width, frame->info.res.height); + frame->frame_info.res.width, frame->frame_info.res.height); ia_css_debug_dtrace(2, " padded width = %d\n", - frame->info.padded_width); - ia_css_debug_dtrace(2, " format = %d\n", frame->info.format); - switch (frame->info.format) { + frame->frame_info.padded_width); + ia_css_debug_dtrace(2, " format = %d\n", frame->frame_info.format); + switch (frame->frame_info.format) { case IA_CSS_FRAME_FORMAT_NV12: case IA_CSS_FRAME_FORMAT_NV16: case IA_CSS_FRAME_FORMAT_NV21: @@ -2565,11 +2564,11 @@ ia_css_debug_pipe_graph_dump_frame( dtrace_dot( "node [shape = box, fixedsize=true, width=2, height=0.7]; \"%p\" [label = \"%s\\n%d(%d) x %d, %dbpp\\n%s\"];", frame, - debug_frame_format2str(frame->info.format), - frame->info.res.width, - frame->info.padded_width, - frame->info.res.height, - frame->info.raw_bit_depth, + debug_frame_format2str(frame->frame_info.format), + frame->frame_info.res.width, + frame->frame_info.padded_width, + frame->frame_info.res.height, + frame->frame_info.raw_bit_depth, bufinfo); if (in_frame) { @@ -2866,10 +2865,10 @@ ia_css_debug_pipe_graph_dump_sp_raw_copy( snprintf(ring_buffer, sizeof(ring_buffer), "node [shape = box, fixedsize=true, width=2, height=0.7]; \"%p\" [label = \"%s\\n%d(%d) x %d\\nRingbuffer\"];", out_frame, - debug_frame_format2str(out_frame->info.format), - out_frame->info.res.width, - out_frame->info.padded_width, - out_frame->info.res.height); + debug_frame_format2str(out_frame->frame_info.format), + out_frame->frame_info.res.width, + out_frame->frame_info.padded_width, + out_frame->frame_info.res.height); dtrace_dot(ring_buffer); @@ -2989,16 +2988,10 @@ ia_css_debug_dump_pipe_config( ia_css_debug_dump_frame_info(&config->vf_output_info[i], "vf_output_info"); } - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "acc_extension: %p\n", - config->acc_extension); - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "num_acc_stages: %d\n", - config->num_acc_stages); ia_css_debug_dump_capture_config(&config->default_capture_config); ia_css_debug_dump_resolution(&config->dvs_envelope, "dvs_envelope"); ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "dvs_frame_delay: %d\n", config->dvs_frame_delay); - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "acc_num_execs: %d\n", - config->acc_num_execs); ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "enable_dz: %d\n", config->enable_dz); IA_CSS_LEAVE_PRIVATE(""); diff --git a/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c b/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c index 5a7058320ee6..83bb42e05421 100644 --- a/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c +++ b/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c @@ -88,12 +88,6 @@ ia_css_elems_bytes_from_info( ** CSS API functions, exposed by ia_css.h **************************************************************************/ -void ia_css_frame_zero(struct ia_css_frame *frame) -{ - assert(frame); - hmm_set(frame->data, 0, frame->data_bytes); -} - int ia_css_frame_allocate_from_info(struct ia_css_frame **frame, const struct ia_css_frame_info *info) { @@ -143,121 +137,6 @@ int ia_css_frame_allocate(struct ia_css_frame **frame, return err; } -int ia_css_frame_map(struct ia_css_frame **frame, - const struct ia_css_frame_info *info, - const void __user *data, - unsigned int pgnr) -{ - int err = 0; - struct ia_css_frame *me; - - assert(frame); - - /* Create the frame structure */ - err = ia_css_frame_create_from_info(&me, info); - - if (err) - return err; - - if (pgnr < ((PAGE_ALIGN(me->data_bytes)) >> PAGE_SHIFT)) { - dev_err(atomisp_dev, - "user space memory size is less than the expected size..\n"); - err = -ENOMEM; - goto error; - } else if (pgnr > ((PAGE_ALIGN(me->data_bytes)) >> PAGE_SHIFT)) { - dev_err(atomisp_dev, - "user space memory size is large than the expected size..\n"); - err = -ENOMEM; - goto error; - } - - me->data = hmm_create_from_userdata(me->data_bytes, data); - if (me->data == mmgr_NULL) - err = -EINVAL; - -error: - if (err) { - kvfree(me); - me = NULL; - } - - *frame = me; - - return err; -} - -int ia_css_frame_create_from_info(struct ia_css_frame **frame, - const struct ia_css_frame_info *info) -{ - int err = 0; - struct ia_css_frame *me; - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "ia_css_frame_create_from_info() enter:\n"); - if (!frame || !info) { - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "ia_css_frame_create_from_info() leave: invalid arguments\n"); - return -EINVAL; - } - - me = frame_create(info->res.width, - info->res.height, - info->format, - info->padded_width, - info->raw_bit_depth, - false); - if (!me) { - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "ia_css_frame_create_from_info() leave: frame create failed\n"); - return -ENOMEM; - } - - err = ia_css_frame_init_planes(me); - - if (err) { - kvfree(me); - me = NULL; - } - - *frame = me; - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "ia_css_frame_create_from_info() leave:\n"); - - return err; -} - -int ia_css_frame_set_data(struct ia_css_frame *frame, - const ia_css_ptr mapped_data, - size_t data_bytes) -{ - int err = 0; - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "ia_css_frame_set_data() enter:\n"); - if (!frame) { - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "ia_css_frame_set_data() leave: NULL frame\n"); - return -EINVAL; - } - - /* If we are setting a valid data. - * Make sure that there is enough - * room for the expected frame format - */ - if ((mapped_data != mmgr_NULL) && (frame->data_bytes > data_bytes)) { - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "ia_css_frame_set_data() leave: invalid arguments\n"); - return -EINVAL; - } - - frame->data = mapped_data; - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "ia_css_frame_set_data() leave:\n"); - - return err; -} - void ia_css_frame_free(struct ia_css_frame *frame) { IA_CSS_ENTER_PRIVATE("frame = %p", frame); @@ -286,32 +165,32 @@ int ia_css_frame_init_planes(struct ia_css_frame *frame) { assert(frame); - switch (frame->info.format) { + switch (frame->frame_info.format) { case IA_CSS_FRAME_FORMAT_MIPI: dev_err(atomisp_dev, "%s: unexpected use of IA_CSS_FRAME_FORMAT_MIPI\n", __func__); return -EINVAL; case IA_CSS_FRAME_FORMAT_RAW_PACKED: frame_init_raw_single_plane(frame, &frame->planes.raw, - frame->info.res.height, - frame->info.padded_width, - frame->info.raw_bit_depth); + frame->frame_info.res.height, + frame->frame_info.padded_width, + frame->frame_info.raw_bit_depth); break; case IA_CSS_FRAME_FORMAT_RAW: frame_init_single_plane(frame, &frame->planes.raw, - frame->info.res.height, - frame->info.padded_width, - frame->info.raw_bit_depth <= 8 ? 1 : 2); + frame->frame_info.res.height, + frame->frame_info.padded_width, + frame->frame_info.raw_bit_depth <= 8 ? 1 : 2); break; case IA_CSS_FRAME_FORMAT_RGB565: frame_init_single_plane(frame, &frame->planes.rgb, - frame->info.res.height, - frame->info.padded_width, 2); + frame->frame_info.res.height, + frame->frame_info.padded_width, 2); break; case IA_CSS_FRAME_FORMAT_RGBA888: frame_init_single_plane(frame, &frame->planes.rgb, - frame->info.res.height, - frame->info.padded_width * 4, 1); + frame->frame_info.res.height, + frame->frame_info.padded_width * 4, 1); break; case IA_CSS_FRAME_FORMAT_PLANAR_RGB888: frame_init_rgb_planes(frame, 1); @@ -324,14 +203,14 @@ int ia_css_frame_init_planes(struct ia_css_frame *frame) case IA_CSS_FRAME_FORMAT_CSI_MIPI_YUV420_8: case IA_CSS_FRAME_FORMAT_CSI_MIPI_LEGACY_YUV420_8: frame_init_single_plane(frame, &frame->planes.yuyv, - frame->info.res.height, - frame->info.padded_width * 2, 1); + frame->frame_info.res.height, + frame->frame_info.padded_width * 2, 1); break; case IA_CSS_FRAME_FORMAT_YUV_LINE: /* Needs 3 extra lines to allow vf_pp prefetching */ frame_init_single_plane(frame, &frame->planes.yuyv, - frame->info.res.height * 3 / 2 + 3, - frame->info.padded_width, 1); + frame->frame_info.res.height * 3 / 2 + 3, + frame->frame_info.padded_width, 1); break; case IA_CSS_FRAME_FORMAT_NV11: frame_init_nv_planes(frame, 4, 1, 1); @@ -380,8 +259,8 @@ int ia_css_frame_init_planes(struct ia_css_frame *frame) break; case IA_CSS_FRAME_FORMAT_BINARY_8: frame_init_single_plane(frame, &frame->planes.binary.data, - frame->info.res.height, - frame->info.padded_width, 1); + frame->frame_info.res.height, + frame->frame_info.padded_width, 1); frame->planes.binary.size = 0; break; default: @@ -510,8 +389,8 @@ bool ia_css_frame_is_same_type(const struct ia_css_frame *frame_a, const struct ia_css_frame *frame_b) { bool is_equal = false; - const struct ia_css_frame_info *info_a = &frame_a->info, - *info_b = &frame_b->info; + const struct ia_css_frame_info *info_a = &frame_a->frame_info; + const struct ia_css_frame_info *info_b = &frame_b->frame_info; ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "ia_css_frame_is_same_type() enter:\n"); @@ -613,8 +492,8 @@ static void frame_init_nv_planes(struct ia_css_frame *frame, unsigned int vertical_decimation, unsigned int bytes_per_element) { - unsigned int y_width = frame->info.padded_width; - unsigned int y_height = frame->info.res.height; + unsigned int y_width = frame->frame_info.padded_width; + unsigned int y_height = frame->frame_info.res.height; unsigned int uv_width; unsigned int uv_height; unsigned int y_bytes; @@ -627,7 +506,7 @@ static void frame_init_nv_planes(struct ia_css_frame *frame, uv_width = 2 * (y_width / horizontal_decimation); uv_height = y_height / vertical_decimation; - if (frame->info.format == IA_CSS_FRAME_FORMAT_NV12_TILEY) { + if (frame->frame_info.format == IA_CSS_FRAME_FORMAT_NV12_TILEY) { y_width = CEIL_MUL(y_width, NV12_TILEY_TILE_WIDTH); uv_width = CEIL_MUL(uv_width, NV12_TILEY_TILE_WIDTH); y_height = CEIL_MUL(y_height, NV12_TILEY_TILE_HEIGHT); @@ -652,8 +531,8 @@ static void frame_init_yuv_planes(struct ia_css_frame *frame, bool swap_uv, unsigned int bytes_per_element) { - unsigned int y_width = frame->info.padded_width, - y_height = frame->info.res.height, + unsigned int y_width = frame->frame_info.padded_width, + y_height = frame->frame_info.res.height, uv_width = y_width / horizontal_decimation, uv_height = y_height / vertical_decimation, y_stride, y_bytes, uv_bytes, uv_stride; @@ -682,8 +561,8 @@ static void frame_init_yuv_planes(struct ia_css_frame *frame, static void frame_init_rgb_planes(struct ia_css_frame *frame, unsigned int bytes_per_element) { - unsigned int width = frame->info.res.width, - height = frame->info.res.height, stride, bytes; + unsigned int width = frame->frame_info.res.width, + height = frame->frame_info.res.height, stride, bytes; stride = width * bytes_per_element; bytes = stride * height; @@ -698,8 +577,8 @@ static void frame_init_rgb_planes(struct ia_css_frame *frame, static void frame_init_qplane6_planes(struct ia_css_frame *frame) { - unsigned int width = frame->info.padded_width / 2, - height = frame->info.res.height / 2, bytes, stride; + unsigned int width = frame->frame_info.padded_width / 2, + height = frame->frame_info.res.height / 2, bytes, stride; stride = width * 2; bytes = stride * height; @@ -781,11 +660,11 @@ static struct ia_css_frame *frame_create(unsigned int width, return NULL; memset(me, 0, sizeof(*me)); - me->info.res.width = width; - me->info.res.height = height; - me->info.format = format; - me->info.padded_width = padded_width; - me->info.raw_bit_depth = raw_bit_depth; + me->frame_info.res.width = width; + me->frame_info.res.height = height; + me->frame_info.format = format; + me->frame_info.padded_width = padded_width; + me->frame_info.raw_bit_depth = raw_bit_depth; me->valid = valid; me->data_bytes = 0; me->data = mmgr_NULL; @@ -847,3 +726,19 @@ void ia_css_resolution_to_sp_resolution( to->width = (uint16_t)from->width; to->height = (uint16_t)from->height; } + +int ia_css_frame_init_from_info(struct ia_css_frame *frame, + const struct ia_css_frame_info *frame_info) +{ + frame->frame_info.res.width = frame_info->res.width; + frame->frame_info.res.height = frame_info->res.height; + frame->frame_info.format = frame_info->format; + frame->frame_info.padded_width = frame_info->padded_width; + frame->frame_info.raw_bit_depth = frame_info->raw_bit_depth; + frame->valid = true; + /* To indicate it is not valid frame. */ + frame->dynamic_queue_id = SH_CSS_INVALID_QUEUE_ID; + frame->buf_type = IA_CSS_BUFFER_TYPE_INVALID; + + return ia_css_frame_init_planes(frame); +} diff --git a/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h b/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h index de2c526a58ae..222c381ff3b9 100644 --- a/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h +++ b/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h @@ -54,7 +54,6 @@ struct ia_css_pipeline { unsigned int inout_port_config; int num_execs; bool acquire_isp_each_stage; - u32 pipe_qos_config; }; #define DEFAULT_PIPELINE { \ @@ -65,7 +64,6 @@ struct ia_css_pipeline { .dvs_frame_delay = IA_CSS_FRAME_DELAY_1, \ .num_execs = -1, \ .acquire_isp_each_stage = true, \ - .pipe_qos_config = QOS_INVALID \ } /* Stage descriptor used to create a new stage in the pipeline */ diff --git a/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c b/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c index dfc50247ea8e..e9e187649a65 100644 --- a/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c +++ b/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c @@ -774,14 +774,6 @@ ia_css_pipeline_configure_inout_port(struct ia_css_pipeline *me, (uint8_t)SH_CSS_PORT_OUTPUT, (uint8_t)SH_CSS_HOST_TYPE, 1); break; - case IA_CSS_PIPE_ID_ACC: - SH_CSS_PIPE_PORT_CONFIG_SET(me->inout_port_config, - (uint8_t)SH_CSS_PORT_INPUT, - (uint8_t)SH_CSS_HOST_TYPE, 1); - SH_CSS_PIPE_PORT_CONFIG_SET(me->inout_port_config, - (uint8_t)SH_CSS_PORT_OUTPUT, - (uint8_t)SH_CSS_HOST_TYPE, 1); - break; default: break; } diff --git a/drivers/staging/media/atomisp/pci/sh_css.c b/drivers/staging/media/atomisp/pci/sh_css.c index da96aaffebc1..726cb7aa4ecd 100644 --- a/drivers/staging/media/atomisp/pci/sh_css.c +++ b/drivers/staging/media/atomisp/pci/sh_css.c @@ -209,13 +209,6 @@ ia_css_pipe_check_format(struct ia_css_pipe *pipe, enum ia_css_frame_format format); /* ISP 2401 */ -static int -ia_css_pipe_load_extension(struct ia_css_pipe *pipe, - struct ia_css_fw_info *firmware); - -static void -ia_css_pipe_unload_extension(struct ia_css_pipe *pipe, - struct ia_css_fw_info *firmware); static void ia_css_reset_defaults(struct sh_css *css); @@ -287,10 +280,6 @@ init_out_frameinfo_defaults(struct ia_css_pipe *pipe, struct ia_css_frame *out_frame, unsigned int idx); static int -sh_css_pipeline_add_acc_stage(struct ia_css_pipeline *pipeline, - const void *acc_fw); - -static int alloc_continuous_frames(struct ia_css_pipe *pipe, bool init_time); static void @@ -329,9 +318,6 @@ create_host_capture_pipeline(struct ia_css_pipe *pipe); static int create_host_yuvpp_pipeline(struct ia_css_pipe *pipe); -static int -create_host_acc_pipeline(struct ia_css_pipe *pipe); - static unsigned int sh_css_get_sw_interrupt_value(unsigned int irq); @@ -362,12 +348,6 @@ static struct sh_css_hmm_buffer_record *sh_css_hmm_buffer_record_validate(ia_css_ptr ddr_buffer_addr, enum ia_css_buffer_type type); -void -ia_css_get_acc_configs( - struct ia_css_pipe *pipe, - struct ia_css_isp_config *config); - - #ifdef ISP2401 static unsigned int get_crop_lines_for_bayer_order(const struct ia_css_stream_config *config); @@ -1649,15 +1629,6 @@ ia_css_enable_isys_event_queue(bool enable) return 0; } -/* For Acceleration API: Flush FW (shared buffer pointer) arguments */ -void -sh_css_flush(struct ia_css_acc_fw *fw) -{ - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "sh_css_flush() enter:\n"); - if ((fw) && (my_css.flush)) - my_css.flush(fw); -} - /* * Mapping sp threads. Currently, this is done when a stream is created and * pipelines are ready to be converted to sp pipelines. Be careful if you are @@ -1670,7 +1641,6 @@ map_sp_threads(struct ia_css_stream *stream, bool map) struct ia_css_pipe *main_pipe = NULL; struct ia_css_pipe *copy_pipe = NULL; struct ia_css_pipe *capture_pipe = NULL; - struct ia_css_pipe *acc_pipe = NULL; int err = 0; enum ia_css_pipe_id pipe_id; @@ -1691,7 +1661,6 @@ map_sp_threads(struct ia_css_stream *stream, bool map) case IA_CSS_PIPE_ID_PREVIEW: copy_pipe = main_pipe->pipe_settings.preview.copy_pipe; capture_pipe = main_pipe->pipe_settings.preview.capture_pipe; - acc_pipe = main_pipe->pipe_settings.preview.acc_pipe; break; case IA_CSS_PIPE_ID_VIDEO: @@ -1700,14 +1669,10 @@ map_sp_threads(struct ia_css_stream *stream, bool map) break; case IA_CSS_PIPE_ID_CAPTURE: - case IA_CSS_PIPE_ID_ACC: default: break; } - if (acc_pipe) - ia_css_pipeline_map(acc_pipe->pipe_num, map); - if (capture_pipe) ia_css_pipeline_map(capture_pipe->pipe_num, map); @@ -1735,7 +1700,6 @@ static int create_host_pipeline_structure(struct ia_css_stream *stream) { struct ia_css_pipe *copy_pipe = NULL, *capture_pipe = NULL; - struct ia_css_pipe *acc_pipe = NULL; enum ia_css_pipe_id pipe_id; struct ia_css_pipe *main_pipe = NULL; int err = 0; @@ -1763,7 +1727,6 @@ create_host_pipeline_structure(struct ia_css_stream *stream) copy_pipe_delay = main_pipe->dvs_frame_delay; capture_pipe = main_pipe->pipe_settings.preview.capture_pipe; capture_pipe_delay = IA_CSS_FRAME_DELAY_0; - acc_pipe = main_pipe->pipe_settings.preview.acc_pipe; err = ia_css_pipeline_create(&main_pipe->pipeline, main_pipe->mode, main_pipe->pipe_num, main_pipe->dvs_frame_delay); break; @@ -1787,11 +1750,6 @@ create_host_pipeline_structure(struct ia_css_stream *stream) main_pipe->pipe_num, main_pipe->dvs_frame_delay); break; - case IA_CSS_PIPE_ID_ACC: - err = ia_css_pipeline_create(&main_pipe->pipeline, main_pipe->mode, - main_pipe->pipe_num, main_pipe->dvs_frame_delay); - break; - default: err = -EINVAL; } @@ -1808,10 +1766,6 @@ create_host_pipeline_structure(struct ia_css_stream *stream) capture_pipe->pipe_num, capture_pipe_delay); - if (!(err) && acc_pipe) - err = ia_css_pipeline_create(&acc_pipe->pipeline, acc_pipe->mode, - acc_pipe->pipe_num, main_pipe->dvs_frame_delay); - /* DH regular multi pipe - not continuous mode: create the next pipelines too */ if (!stream->config.continuous) { int i; @@ -1837,7 +1791,6 @@ static int create_host_pipeline(struct ia_css_stream *stream) { struct ia_css_pipe *copy_pipe = NULL, *capture_pipe = NULL; - struct ia_css_pipe *acc_pipe = NULL; enum ia_css_pipe_id pipe_id; struct ia_css_pipe *main_pipe = NULL; int err = 0; @@ -1881,27 +1834,17 @@ create_host_pipeline(struct ia_css_stream *stream) } } -#if !defined(ISP2401) /* old isys: need to allocate_mipi_frames() even in IA_CSS_PIPE_MODE_COPY */ - if (pipe_id != IA_CSS_PIPE_ID_ACC) { - err = allocate_mipi_frames(main_pipe, &stream->info); - if (err) - goto ERR; - } -#elif defined(ISP2401) - if ((pipe_id != IA_CSS_PIPE_ID_ACC) && - (main_pipe->config.mode != IA_CSS_PIPE_MODE_COPY)) { + if (!IS_ISP2401 || main_pipe->config.mode != IA_CSS_PIPE_MODE_COPY) { err = allocate_mipi_frames(main_pipe, &stream->info); if (err) goto ERR; } -#endif switch (pipe_id) { case IA_CSS_PIPE_ID_PREVIEW: copy_pipe = main_pipe->pipe_settings.preview.copy_pipe; capture_pipe = main_pipe->pipe_settings.preview.capture_pipe; - acc_pipe = main_pipe->pipe_settings.preview.acc_pipe; max_input_width = main_pipe->pipe_settings.preview.preview_binary.info->sp.input.max_width; @@ -1935,12 +1878,6 @@ create_host_pipeline(struct ia_css_stream *stream) break; - case IA_CSS_PIPE_ID_ACC: - err = create_host_acc_pipeline(main_pipe); - if (err) - goto ERR; - - break; default: err = -EINVAL; } @@ -1960,12 +1897,6 @@ create_host_pipeline(struct ia_css_stream *stream) goto ERR; } - if (acc_pipe) { - err = create_host_acc_pipeline(acc_pipe); - if (err) - goto ERR; - } - /* DH regular multi pipe - not continuous mode: create the next pipelines too */ if (!stream->config.continuous) { int i; @@ -1984,9 +1915,6 @@ create_host_pipeline(struct ia_css_stream *stream) case IA_CSS_PIPE_ID_YUVPP: err = create_host_yuvpp_pipeline(stream->pipes[i]); break; - case IA_CSS_PIPE_ID_ACC: - err = create_host_acc_pipeline(stream->pipes[i]); - break; default: err = -EINVAL; } @@ -2037,9 +1965,6 @@ init_pipe_defaults(enum ia_css_pipe_mode mode, pipe->mode = IA_CSS_PIPE_ID_VIDEO; memcpy(&pipe->pipe_settings.video, &video, sizeof(video)); break; - case IA_CSS_PIPE_MODE_ACC: - pipe->mode = IA_CSS_PIPE_ID_ACC; - break; case IA_CSS_PIPE_MODE_COPY: pipe->mode = IA_CSS_PIPE_ID_CAPTURE; break; @@ -2156,27 +2081,6 @@ find_pipe_by_num(uint32_t pipe_num) return NULL; } -static void sh_css_pipe_free_acc_binaries( - struct ia_css_pipe *pipe) -{ - struct ia_css_pipeline *pipeline; - struct ia_css_pipeline_stage *stage; - - if (!pipe) { - IA_CSS_ERROR("NULL input pointer"); - return; - } - pipeline = &pipe->pipeline; - - /* loop through the stages and unload them */ - for (stage = pipeline->stages; stage; stage = stage->next) { - struct ia_css_fw_info *firmware = (struct ia_css_fw_info *) - stage->firmware; - if (firmware) - ia_css_pipe_unload_extension(pipe, firmware); - } -} - int ia_css_pipe_destroy(struct ia_css_pipe *pipe) { @@ -2241,9 +2145,6 @@ ia_css_pipe_destroy(struct ia_css_pipe *pipe) ia_css_frame_free_multiple(MAX_NUM_VIDEO_DELAY_FRAMES, pipe->pipe_settings.capture.delay_frames); break; - case IA_CSS_PIPE_MODE_ACC: - sh_css_pipe_free_acc_binaries(pipe); - break; case IA_CSS_PIPE_MODE_COPY: break; case IA_CSS_PIPE_MODE_YUVPP: @@ -2261,10 +2162,6 @@ ia_css_pipe_destroy(struct ia_css_pipe *pipe) ia_css_pipeline_destroy(&pipe->pipeline); pipe_release_pipe_num(ia_css_pipe_get_pipe_num(pipe)); - /* Temporarily, not every sh_css_pipe has an acc_extension. */ - if (pipe->config.acc_extension) - ia_css_pipe_unload_extension(pipe, pipe->config.acc_extension); - kfree(pipe); IA_CSS_LEAVE("err = %d", err); return err; @@ -3060,7 +2957,7 @@ init_vf_frameinfo_defaults(struct ia_css_pipe *pipe, assert(vf_frame); - sh_css_pipe_get_viewfinder_frame_info(pipe, &vf_frame->info, idx); + sh_css_pipe_get_viewfinder_frame_info(pipe, &vf_frame->frame_info, idx); vf_frame->flash_state = IA_CSS_FRAME_FLASH_STATE_NONE; ia_css_pipeline_get_sp_thread_id(ia_css_pipe_get_pipe_num(pipe), &thread_id); ia_css_query_internal_queue_id(IA_CSS_BUFFER_TYPE_VF_OUTPUT_FRAME + idx, thread_id, &queue_id); @@ -3229,31 +3126,31 @@ init_in_frameinfo_memory_defaults(struct ia_css_pipe *pipe, assert(frame); in_frame = frame; - in_frame->info.format = format; + in_frame->frame_info.format = format; #ifdef ISP2401 if (format == IA_CSS_FRAME_FORMAT_RAW) - in_frame->info.format = (pipe->stream->config.pack_raw_pixels) ? + in_frame->frame_info.format = (pipe->stream->config.pack_raw_pixels) ? IA_CSS_FRAME_FORMAT_RAW_PACKED : IA_CSS_FRAME_FORMAT_RAW; #endif - in_frame->info.res.width = pipe->stream->config.input_config.input_res.width; - in_frame->info.res.height = pipe->stream->config.input_config.input_res.height; - in_frame->info.raw_bit_depth = - ia_css_pipe_util_pipe_input_format_bpp(pipe); - ia_css_frame_info_set_width(&in_frame->info, pipe->stream->config.input_config.input_res.width, 0); + in_frame->frame_info.res.width = pipe->stream->config.input_config.input_res.width; + in_frame->frame_info.res.height = pipe->stream->config.input_config.input_res.height; + in_frame->frame_info.raw_bit_depth = ia_css_pipe_util_pipe_input_format_bpp(pipe); + ia_css_frame_info_set_width(&in_frame->frame_info, + pipe->stream->config.input_config.input_res.width, 0); in_frame->flash_state = IA_CSS_FRAME_FLASH_STATE_NONE; ia_css_pipeline_get_sp_thread_id(ia_css_pipe_get_pipe_num(pipe), &thread_id); ia_css_query_internal_queue_id(IA_CSS_BUFFER_TYPE_INPUT_FRAME, thread_id, &queue_id); in_frame->dynamic_queue_id = queue_id; in_frame->buf_type = IA_CSS_BUFFER_TYPE_INPUT_FRAME; #ifdef ISP2401 - ia_css_get_crop_offsets(pipe, &in_frame->info); + ia_css_get_crop_offsets(pipe, &in_frame->frame_info); #endif err = ia_css_frame_init_planes(in_frame); - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE, - "init_in_frameinfo_memory_defaults() bayer_order = %d:\n", in_frame->info.raw_bayer_order); + ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE, "%s() bayer_order = %d\n", + __func__, in_frame->frame_info.raw_bayer_order); return err; } @@ -3268,7 +3165,7 @@ init_out_frameinfo_defaults(struct ia_css_pipe *pipe, assert(out_frame); - sh_css_pipe_get_output_frame_info(pipe, &out_frame->info, idx); + sh_css_pipe_get_output_frame_info(pipe, &out_frame->frame_info, idx); out_frame->flash_state = IA_CSS_FRAME_FLASH_STATE_NONE; ia_css_pipeline_get_sp_thread_id(ia_css_pipe_get_pipe_num(pipe), &thread_id); ia_css_query_internal_queue_id(IA_CSS_BUFFER_TYPE_OUTPUT_FRAME + idx, thread_id, &queue_id); @@ -3433,31 +3330,6 @@ static int create_host_video_pipeline(struct ia_css_pipe *pipe) } } - /* Append Extension on Video out, if enabled */ - if (!need_vf_pp && video_stage && pipe->config.acc_extension && - (pipe->config.acc_extension->info.isp.type == IA_CSS_ACC_OUTPUT)) { - struct ia_css_frame *out = NULL; - struct ia_css_frame *in = NULL; - - if ((pipe->config.acc_extension->info.isp.sp.enable.output) && - (pipe->config.acc_extension->info.isp.sp.enable.in_frame) && - (pipe->config.acc_extension->info.isp.sp.enable.out_frame)) { - /* In/Out Frame mapping to support output frame extension.*/ - out = video_stage->args.out_frame[0]; - err = ia_css_frame_allocate_from_info(&in, &pipe->output_info[0]); - if (err) - goto ERR; - video_stage->args.out_frame[0] = in; - } - - err = add_firmwares(me, video_binary, pipe->output_stage, - last_output_firmware(pipe->output_stage), - IA_CSS_BINARY_MODE_VIDEO, - in, out, NULL, &video_stage, NULL); - if (err) - goto ERR; - } - if (need_yuv_pp && video_stage) { struct ia_css_frame *tmp_in_frame = video_stage->args.out_frame[0]; struct ia_css_frame *tmp_out_frame = NULL; @@ -3489,45 +3361,6 @@ ERR: return err; } -static int -create_host_acc_pipeline(struct ia_css_pipe *pipe) -{ - int err = 0; - const struct ia_css_fw_info *fw; - unsigned int i; - - IA_CSS_ENTER_PRIVATE("pipe = %p", pipe); - if ((!pipe) || (!pipe->stream)) { - IA_CSS_LEAVE_ERR_PRIVATE(-EINVAL); - return -EINVAL; - } - - pipe->pipeline.num_execs = pipe->config.acc_num_execs; - /* Reset pipe_qos_config to default disable all QOS extension stages */ - if (pipe->config.acc_extension) - pipe->pipeline.pipe_qos_config = 0; - - for (fw = pipe->vf_stage; fw; fw = fw->next) { - err = sh_css_pipeline_add_acc_stage(&pipe->pipeline, fw); - if (err) - goto ERR; - } - - for (i = 0; i < pipe->config.num_acc_stages; i++) { - struct ia_css_fw_info *fw = pipe->config.acc_stages[i]; - - err = sh_css_pipeline_add_acc_stage(&pipe->pipeline, fw); - if (err) - goto ERR; - } - - ia_css_pipeline_finalize_stages(&pipe->pipeline, pipe->stream->config.continuous); - -ERR: - IA_CSS_LEAVE_ERR_PRIVATE(err); - return err; -} - /* Create stages for preview */ static int create_host_preview_pipeline(struct ia_css_pipe *pipe) @@ -3690,7 +3523,6 @@ preview_start(struct ia_css_pipe *pipe) { int err = 0; struct ia_css_pipe *copy_pipe, *capture_pipe; - struct ia_css_pipe *acc_pipe; enum sh_css_pipe_config_override copy_ovrd; enum ia_css_input_mode preview_pipe_input_mode; unsigned int thread_id; @@ -3705,7 +3537,6 @@ preview_start(struct ia_css_pipe *pipe) copy_pipe = pipe->pipe_settings.preview.copy_pipe; capture_pipe = pipe->pipe_settings.preview.capture_pipe; - acc_pipe = pipe->pipe_settings.preview.acc_pipe; sh_css_metrics_start_frame(); @@ -3764,22 +3595,6 @@ preview_start(struct ia_css_pipe *pipe) (enum mipi_port_id)0); } - if (acc_pipe) { - sh_css_sp_init_pipeline(&acc_pipe->pipeline, - IA_CSS_PIPE_ID_ACC, - (uint8_t)ia_css_pipe_get_pipe_num(acc_pipe), - false, - pipe->stream->config.pixels_per_clock == 2, - false, /* continuous */ - false, /* offline */ - pipe->required_bds_factor, - 0, - IA_CSS_INPUT_MODE_MEMORY, - NULL, - NULL, - (enum mipi_port_id)0); - } - start_pipe(pipe, copy_ovrd, preview_pipe_input_mode); IA_CSS_LEAVE_ERR_PRIVATE(err); @@ -3850,9 +3665,7 @@ ia_css_pipe_enqueue_buffer(struct ia_css_pipe *pipe, pipeline = &pipe->pipeline; - assert(pipeline || - pipe_id == IA_CSS_PIPE_ID_COPY || - pipe_id == IA_CSS_PIPE_ID_ACC); + assert(pipeline || pipe_id == IA_CSS_PIPE_ID_COPY); assert(sizeof(NULL) <= sizeof(ddr_buffer.kernel_ptr)); ddr_buffer.kernel_ptr = HOST_ADDRESS(NULL); @@ -4146,7 +3959,7 @@ ia_css_pipe_dequeue_buffer(struct ia_css_pipe *pipe, if (!frame->valid) pipe->num_invalid_frames--; - if (frame->info.format == IA_CSS_FRAME_FORMAT_BINARY_8) { + if (frame->frame_info.format == IA_CSS_FRAME_FORMAT_BINARY_8) { #ifdef ISP2401 frame->planes.binary.size = frame->data_bytes; #else @@ -4442,16 +4255,6 @@ ia_css_dequeue_isys_event(struct ia_css_event *event) return err; } -static void -acc_start(struct ia_css_pipe *pipe) -{ - assert(pipe); - assert(pipe->stream); - - start_pipe(pipe, SH_CSS_PIPE_CONFIG_OVRD_NO_OVRD, - pipe->stream->config.mode); -} - static int sh_css_pipe_start(struct ia_css_stream *stream) { @@ -4496,9 +4299,6 @@ sh_css_pipe_start(struct ia_css_stream *stream) case IA_CSS_PIPE_ID_YUVPP: err = yuvpp_start(pipe); break; - case IA_CSS_PIPE_ID_ACC: - acc_start(pipe); - break; default: err = -EINVAL; } @@ -4524,10 +4324,6 @@ sh_css_pipe_start(struct ia_css_stream *stream) stream->pipes[i]->stop_requested = false; err = yuvpp_start(stream->pipes[i]); break; - case IA_CSS_PIPE_ID_ACC: - stream->pipes[i]->stop_requested = false; - acc_start(stream->pipes[i]); - break; default: err = -EINVAL; } @@ -4620,22 +4416,6 @@ sh_css_pipe_start(struct ia_css_stream *stream) (uint8_t)thread_id, 0, 0); } - /* in case of PREVIEW mode, check whether QOS acc_pipe is available, then start the qos pipe */ - if (pipe_id == IA_CSS_PIPE_ID_PREVIEW) { - struct ia_css_pipe *acc_pipe = NULL; - - acc_pipe = pipe->pipe_settings.preview.acc_pipe; - - if (acc_pipe) { - ia_css_pipeline_get_sp_thread_id(ia_css_pipe_get_pipe_num(acc_pipe), - &thread_id); - /* by the time we reach here q is initialized and handle is available.*/ - ia_css_bufq_enqueue_psys_event( - IA_CSS_PSYS_SW_EVENT_START_STREAM, - (uint8_t)thread_id, 0, 0); - } - } - stream->started = true; IA_CSS_LEAVE_ERR_PRIVATE(err); @@ -6861,8 +6641,6 @@ sh_css_pipe_load_binaries(struct ia_css_pipe *pipe) case IA_CSS_PIPE_ID_YUVPP: err = load_yuvpp_binaries(pipe); break; - case IA_CSS_PIPE_ID_ACC: - break; default: err = -EINVAL; break; @@ -7102,7 +6880,7 @@ create_host_yuvpp_pipeline(struct ia_css_pipe *pipe) /* we use output port 1 as internal output port */ tmp_in_frame = yuv_scaler_stage->args.out_frame[1]; if (pipe->pipe_settings.yuvpp.is_output_stage[i]) { - if (tmp_vf_frame && (tmp_vf_frame->info.res.width != 0)) { + if (tmp_vf_frame && (tmp_vf_frame->frame_info.res.width != 0)) { in_frame = yuv_scaler_stage->args.out_vf_frame; err = add_vf_pp_stage(pipe, in_frame, tmp_vf_frame, @@ -7118,7 +6896,7 @@ create_host_yuvpp_pipeline(struct ia_css_pipe *pipe) } } } else if (copy_stage) { - if (vf_frame[0] && vf_frame[0]->info.res.width != 0) { + if (vf_frame[0] && vf_frame[0]->frame_info.res.width != 0) { in_frame = copy_stage->args.out_vf_frame; err = add_vf_pp_stage(pipe, in_frame, vf_frame[0], &vf_pp_binary[0], &vf_pp_stage); @@ -7158,10 +6936,10 @@ create_host_copy_pipeline(struct ia_css_pipe *pipe, if (copy_on_sp(pipe) && pipe->stream->config.input_config.format == ATOMISP_INPUT_FORMAT_BINARY_8) { - ia_css_frame_info_init(&out_frame->info, JPEG_BYTES, 1, + ia_css_frame_info_init(&out_frame->frame_info, JPEG_BYTES, 1, IA_CSS_FRAME_FORMAT_BINARY_8, 0); - } else if (out_frame->info.format == IA_CSS_FRAME_FORMAT_RAW) { - out_frame->info.raw_bit_depth = + } else if (out_frame->frame_info.format == IA_CSS_FRAME_FORMAT_RAW) { + out_frame->frame_info.raw_bit_depth = ia_css_pipe_util_pipe_input_format_bpp(pipe); } @@ -7200,7 +6978,7 @@ create_host_isyscopy_capture_pipeline(struct ia_css_pipe *pipe) ia_css_pipeline_clean(me); /* Construct out_frame info */ - err = sh_css_pipe_get_output_frame_info(pipe, &out_frame->info, 0); + err = sh_css_pipe_get_output_frame_info(pipe, &out_frame->frame_info, 0); if (err) return err; out_frame->flash_state = IA_CSS_FRAME_FLASH_STATE_NONE; @@ -7755,154 +7533,6 @@ ia_css_stream_end_input_frame(const struct ia_css_stream *stream) ia_css_inputfifo_end_frame(stream->config.channel_id); } -static void -append_firmware(struct ia_css_fw_info **l, struct ia_css_fw_info *firmware) -{ - IA_CSS_ENTER_PRIVATE("l = %p, firmware = %p", l, firmware); - if (!l) { - IA_CSS_ERROR("NULL fw_info"); - IA_CSS_LEAVE_PRIVATE(""); - return; - } - while (*l) - l = &(*l)->next; - *l = firmware; - /* when multiple acc extensions are loaded, 'next' can be not NULL */ - /*firmware->next = NULL;*/ - IA_CSS_LEAVE_PRIVATE(""); -} - -static void -remove_firmware(struct ia_css_fw_info **l, struct ia_css_fw_info *firmware) -{ - assert(*l); - assert(firmware); - (void)l; - (void)firmware; - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE, "remove_firmware() enter:\n"); - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE, "remove_firmware() leave:\n"); - return; /* removing single and multiple firmware is handled in acc_unload_extension() */ -} - -static int upload_isp_code(struct ia_css_fw_info *firmware) -{ - ia_css_ptr binary; - - if (!firmware) { - IA_CSS_ERROR("NULL input parameter"); - return -EINVAL; - } - binary = firmware->info.isp.xmem_addr; - - if (!binary) { - unsigned int size = firmware->blob.size; - const unsigned char *blob; - const unsigned char *binary_name; - - binary_name = - (const unsigned char *)(IA_CSS_EXT_ISP_PROG_NAME( - firmware)); - blob = binary_name + - strlen((const char *)binary_name) + - 1; - binary = sh_css_load_blob(blob, size); - firmware->info.isp.xmem_addr = binary; - } - - if (!binary) - return -ENOMEM; - return 0; -} - -static int -acc_load_extension(struct ia_css_fw_info *firmware) -{ - int err; - struct ia_css_fw_info *hd = firmware; - - while (hd) { - err = upload_isp_code(hd); - if (err) - return err; - hd = hd->next; - } - - if (!firmware) - return -EINVAL; - firmware->loaded = true; - return 0; -} - -static void -acc_unload_extension(struct ia_css_fw_info *firmware) -{ - struct ia_css_fw_info *hd = firmware; - struct ia_css_fw_info *hdn = NULL; - - if (!firmware) /* should not happen */ - return; - /* unload and remove multiple firmwares */ - while (hd) { - hdn = (hd->next) ? &(*hd->next) : NULL; - if (hd->info.isp.xmem_addr) { - hmm_free(hd->info.isp.xmem_addr); - hd->info.isp.xmem_addr = mmgr_NULL; - } - hd->isp_code = NULL; - hd->next = NULL; - hd = hdn; - } - - firmware->loaded = false; -} - -/* Load firmware for extension */ -static int -ia_css_pipe_load_extension(struct ia_css_pipe *pipe, - struct ia_css_fw_info *firmware) -{ - int err = 0; - - IA_CSS_ENTER_PRIVATE("fw = %p pipe = %p", firmware, pipe); - - if ((!firmware) || (!pipe)) { - IA_CSS_LEAVE_ERR_PRIVATE(-EINVAL); - return -EINVAL; - } - - if (firmware->info.isp.type == IA_CSS_ACC_OUTPUT) - append_firmware(&pipe->output_stage, firmware); - else if (firmware->info.isp.type == IA_CSS_ACC_VIEWFINDER) - append_firmware(&pipe->vf_stage, firmware); - err = acc_load_extension(firmware); - - IA_CSS_LEAVE_ERR_PRIVATE(err); - return err; -} - -/* Unload firmware for extension */ -static void -ia_css_pipe_unload_extension(struct ia_css_pipe *pipe, - struct ia_css_fw_info *firmware) -{ - IA_CSS_ENTER_PRIVATE("fw = %p pipe = %p", firmware, pipe); - - if ((!firmware) || (!pipe)) { - IA_CSS_ERROR("NULL input parameters"); - IA_CSS_LEAVE_PRIVATE(""); - return; - } - - if (firmware->info.isp.type == IA_CSS_ACC_OUTPUT) - remove_firmware(&pipe->output_stage, firmware); - else if (firmware->info.isp.type == IA_CSS_ACC_VIEWFINDER) - remove_firmware(&pipe->vf_stage, firmware); - acc_unload_extension(firmware); - - IA_CSS_LEAVE_PRIVATE(""); -} - bool ia_css_pipeline_uses_params(struct ia_css_pipeline *me) { @@ -7924,35 +7554,6 @@ ia_css_pipeline_uses_params(struct ia_css_pipeline *me) return false; } -static int -sh_css_pipeline_add_acc_stage(struct ia_css_pipeline *pipeline, - const void *acc_fw) -{ - struct ia_css_fw_info *fw = (struct ia_css_fw_info *)acc_fw; - /* In QoS case, load_extension already called, so skipping */ - int err = 0; - - if (!fw->loaded) - err = acc_load_extension(fw); - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "sh_css_pipeline_add_acc_stage() enter: pipeline=%p, acc_fw=%p\n", - pipeline, acc_fw); - - if (!err) { - struct ia_css_pipeline_stage_desc stage_desc; - - ia_css_pipe_get_acc_stage_desc(&stage_desc, NULL, fw); - err = ia_css_pipeline_create_and_add_stage(pipeline, - &stage_desc, - NULL); - } - - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "sh_css_pipeline_add_acc_stage() leave: return_err=%d\n", err); - return err; -} - /* * @brief Tag a specific frame in continuous capture. * Refer to "sh_css_internal.h" for details. @@ -8177,26 +7778,6 @@ void ia_css_stream_config_defaults(struct ia_css_stream_config *stream_config) stream_config->source.port.rxcount = 0x04040404; } -static int -ia_css_acc_pipe_create(struct ia_css_pipe *pipe) -{ - int err = 0; - - if (!pipe) { - IA_CSS_ERROR("NULL input parameter"); - return -EINVAL; - } - - /* There is not meaning for num_execs = 0 semantically. Run at least once. */ - if (pipe->config.acc_num_execs == 0) - pipe->config.acc_num_execs = 1; - - if (pipe->config.acc_extension) - err = ia_css_pipe_load_extension(pipe, pipe->config.acc_extension); - - return err; -} - int ia_css_pipe_create(const struct ia_css_pipe_config *config, struct ia_css_pipe **pipe) { @@ -8257,23 +7838,6 @@ ia_css_pipe_create_extra(const struct ia_css_pipe_config *config, else ia_css_pipe_extra_config_defaults(&internal_pipe->extra_config); - if (config->mode == IA_CSS_PIPE_MODE_ACC) { - /* - * Temporary hack to migrate acceleration to CSS 2.0. - * In the future the code for all pipe types should be - * unified. - */ - *pipe = internal_pipe; - if (!internal_pipe->config.acc_extension && - internal_pipe->config.num_acc_stages == - 0) { /* if no acc binary and no standalone stage */ - *pipe = NULL; - IA_CSS_LEAVE_ERR_PRIVATE(0); - return 0; - } - return ia_css_acc_pipe_create(internal_pipe); - } - /* * Use config value when dvs_frame_delay setting equal to 2, * otherwise always 1 by default @@ -8368,15 +7932,6 @@ ia_css_pipe_create_extra(const struct ia_css_pipe_config *config, } } } - if (internal_pipe->config.acc_extension) { - err = ia_css_pipe_load_extension(internal_pipe, - internal_pipe->config.acc_extension); - if (err) { - IA_CSS_LEAVE_ERR_PRIVATE(err); - kvfree(internal_pipe); - return err; - } - } /* set all info to zeroes first */ memset(&internal_pipe->info, 0, sizeof(internal_pipe->info)); @@ -8525,57 +8080,6 @@ find_pipe(struct ia_css_pipe *pipes[], unsigned int num_pipes, } static int -ia_css_acc_stream_create(struct ia_css_stream *stream) -{ - int i; - int err = 0; - - IA_CSS_ENTER_PRIVATE("stream = %p", stream); - - if (!stream) { - IA_CSS_LEAVE_ERR_PRIVATE(-EINVAL); - return -EINVAL; - } - - for (i = 0; i < stream->num_pipes; i++) { - struct ia_css_pipe *pipe = stream->pipes[i]; - - if (!pipe) { - IA_CSS_LEAVE_ERR_PRIVATE(-EINVAL); - return -EINVAL; - } - - pipe->stream = stream; - } - - /* Map SP threads before doing anything. */ - err = map_sp_threads(stream, true); - if (err) { - IA_CSS_LEAVE_ERR_PRIVATE(err); - return err; - } - - for (i = 0; i < stream->num_pipes; i++) { - struct ia_css_pipe *pipe = stream->pipes[i]; - - assert(pipe); - ia_css_pipe_map_queue(pipe, true); - } - - err = create_host_pipeline_structure(stream); - if (err) { - IA_CSS_LEAVE_ERR_PRIVATE(err); - return err; - } - - stream->started = false; - - IA_CSS_LEAVE_ERR_PRIVATE(0); - - return 0; -} - -static int metadata_info_init(const struct ia_css_metadata_config *mdc, struct ia_css_metadata_info *md) { @@ -8807,11 +8311,6 @@ ia_css_stream_create(const struct ia_css_stream_config *stream_config, goto ERR; IA_CSS_LOG("isp_params_configs: %p", curr_stream->isp_params_configs); - if (num_pipes == 1 && pipes[0]->config.mode == IA_CSS_PIPE_MODE_ACC) { - *stream = curr_stream; - err = ia_css_acc_stream_create(curr_stream); - goto ERR; - } /* sensor binning */ if (!spcopyonly) { sensor_binning_changed = @@ -8832,7 +8331,6 @@ ia_css_stream_create(const struct ia_css_stream_config *stream_config, /* Search for the preview pipe and create the copy pipe */ struct ia_css_pipe *preview_pipe; struct ia_css_pipe *video_pipe; - struct ia_css_pipe *acc_pipe; struct ia_css_pipe *capture_pipe = NULL; struct ia_css_pipe *copy_pipe = NULL; @@ -8847,11 +8345,7 @@ ia_css_stream_create(const struct ia_css_stream_config *stream_config, IA_CSS_PIPE_MODE_PREVIEW, false); video_pipe = find_pipe(pipes, num_pipes, IA_CSS_PIPE_MODE_VIDEO, false); - acc_pipe = find_pipe(pipes, num_pipes, IA_CSS_PIPE_MODE_ACC, - false); - if (acc_pipe && num_pipes == 2 && curr_stream->cont_capt) - curr_stream->cont_capt = - false; /* preview + QoS case will not need cont_capt switch */ + if (curr_stream->cont_capt) { capture_pipe = find_pipe(pipes, num_pipes, IA_CSS_PIPE_MODE_CAPTURE, @@ -8888,9 +8382,6 @@ ia_css_stream_create(const struct ia_css_stream_config *stream_config, } if (video_pipe && curr_stream->cont_capt) video_pipe->pipe_settings.video.capture_pipe = capture_pipe; - - if (preview_pipe && acc_pipe) - preview_pipe->pipe_settings.preview.acc_pipe = acc_pipe; } for (i = 0; i < num_pipes; i++) { curr_pipe = pipes[i]; @@ -9738,13 +9229,6 @@ void ia_css_pipe_map_queue(struct ia_css_pipe *pipe, bool map) if (!pipe->stream->config.continuous) ia_css_queue_map(thread_id, IA_CSS_BUFFER_TYPE_OUTPUT_FRAME, map); ia_css_queue_map(thread_id, IA_CSS_BUFFER_TYPE_METADATA, map); - } else if (pipe->mode == IA_CSS_PIPE_ID_ACC) { - if (need_input_queue) - ia_css_queue_map(thread_id, IA_CSS_BUFFER_TYPE_INPUT_FRAME, map); - ia_css_queue_map(thread_id, IA_CSS_BUFFER_TYPE_OUTPUT_FRAME, map); - ia_css_queue_map(thread_id, IA_CSS_BUFFER_TYPE_PARAMETER_SET, map); - ia_css_queue_map(thread_id, IA_CSS_BUFFER_TYPE_PER_FRAME_PARAMETER_SET, map); - ia_css_queue_map(thread_id, IA_CSS_BUFFER_TYPE_METADATA, map); } else if (pipe->mode == IA_CSS_PIPE_ID_YUVPP) { unsigned int idx; @@ -9795,92 +9279,6 @@ ia_css_unlock_raw_frame(struct ia_css_stream *stream, uint32_t exp_id) return ret; } -/* - * @brief Set the state (Enable or Disable) of the Extension stage in the - * given pipe. - */ -int -ia_css_pipe_set_qos_ext_state(struct ia_css_pipe *pipe, uint32_t fw_handle, - bool enable) -{ - unsigned int thread_id; - struct ia_css_pipeline_stage *stage; - int err = 0; - - IA_CSS_ENTER(""); - - /* Parameter Check */ - if (!pipe || !pipe->stream) { - IA_CSS_ERROR("Invalid Pipe."); - err = -EINVAL; - } else if (!(pipe->config.acc_extension)) { - IA_CSS_ERROR("Invalid Pipe(No Extension Firmware)"); - err = -EINVAL; - } else if (!sh_css_sp_is_running()) { - IA_CSS_ERROR("Leaving: queue unavailable."); - err = -EBUSY; - } else { - /* Query the threadid and stage_num for the Extension firmware*/ - ia_css_pipeline_get_sp_thread_id(ia_css_pipe_get_pipe_num(pipe), &thread_id); - err = ia_css_pipeline_get_stage_from_fw(&pipe->pipeline, fw_handle, &stage); - if (!err) { - /* Set the Extension State;. TODO: Add check for stage firmware.type (QOS)*/ - err = ia_css_bufq_enqueue_psys_event( - (uint8_t)IA_CSS_PSYS_SW_EVENT_STAGE_ENABLE_DISABLE, - (uint8_t)thread_id, - (uint8_t)stage->stage_num, - enable ? 1 : 0); - if (!err) { - if (enable) - SH_CSS_QOS_STAGE_ENABLE(&sh_css_sp_group.pipe[thread_id], stage->stage_num); - else - SH_CSS_QOS_STAGE_DISABLE(&sh_css_sp_group.pipe[thread_id], stage->stage_num); - } - } - } - IA_CSS_LEAVE("err:%d handle:%u enable:%d", err, fw_handle, enable); - return err; -} - -/* - * @brief Get the state (Enable or Disable) of the Extension stage in the - * given pipe. - */ -int -ia_css_pipe_get_qos_ext_state(struct ia_css_pipe *pipe, uint32_t fw_handle, - bool *enable) -{ - struct ia_css_pipeline_stage *stage; - unsigned int thread_id; - int err = 0; - - IA_CSS_ENTER(""); - - /* Parameter Check */ - if (!pipe || !pipe->stream) { - IA_CSS_ERROR("Invalid Pipe."); - err = -EINVAL; - } else if (!(pipe->config.acc_extension)) { - IA_CSS_ERROR("Invalid Pipe (No Extension Firmware)."); - err = -EINVAL; - } else if (!sh_css_sp_is_running()) { - IA_CSS_ERROR("Leaving: queue unavailable."); - err = -EBUSY; - } else { - /* Query the threadid and stage_num corresponding to the Extension firmware*/ - ia_css_pipeline_get_sp_thread_id(ia_css_pipe_get_pipe_num(pipe), &thread_id); - err = ia_css_pipeline_get_stage_from_fw(&pipe->pipeline, fw_handle, &stage); - - if (!err) { - /* Get the Extension State */ - *enable = (SH_CSS_QOS_STAGE_IS_ENABLED(&sh_css_sp_group.pipe[thread_id], - stage->stage_num)) ? true : false; - } - } - IA_CSS_LEAVE("err:%d handle:%u enable:%d", err, fw_handle, *enable); - return err; -} - static void sh_css_hmm_buffer_record_init(void) { diff --git a/drivers/staging/media/atomisp/pci/sh_css_internal.h b/drivers/staging/media/atomisp/pci/sh_css_internal.h index 435b3cedd1c3..d98f1323441e 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_internal.h +++ b/drivers/staging/media/atomisp/pci/sh_css_internal.h @@ -488,16 +488,6 @@ ia_css_metadata_free_multiple(unsigned int num_bufs, /* Macro for handling pipe_qos_config */ #define QOS_INVALID (~0U) -#define QOS_ALL_STAGES_DISABLED (0U) -#define QOS_STAGE_MASK(num) (0x00000001 << num) -#define SH_CSS_IS_QOS_PIPE(pipe) ((pipe)->pipe_qos_config != QOS_INVALID) -#define SH_CSS_QOS_STAGE_ENABLE(pipe, num) ((pipe)->pipe_qos_config |= QOS_STAGE_MASK(num)) -#define SH_CSS_QOS_STAGE_DISABLE(pipe, num) ((pipe)->pipe_qos_config &= ~QOS_STAGE_MASK(num)) -#define SH_CSS_QOS_STAGE_IS_ENABLED(pipe, num) ((pipe)->pipe_qos_config & QOS_STAGE_MASK(num)) -#define SH_CSS_QOS_STAGE_IS_ALL_DISABLED(pipe) ((pipe)->pipe_qos_config == QOS_ALL_STAGES_DISABLED) -#define SH_CSS_QOS_MODE_PIPE_ADD(mode, pipe) ((mode) |= (0x1 << (pipe)->pipe_id)) -#define SH_CSS_QOS_MODE_PIPE_REMOVE(mode, pipe) ((mode) &= ~(0x1 << (pipe)->pipe_id)) -#define SH_CSS_IS_QOS_ONLY_MODE(mode) ((mode) == (0x1 << IA_CSS_PIPE_ID_ACC)) /* Information for a pipeline */ struct sh_css_sp_pipeline { @@ -907,9 +897,6 @@ sh_css_params_init(void); void sh_css_params_uninit(void); -/* For Acceleration API: Flush FW (shared buffer pointer) arguments */ -void sh_css_flush(struct ia_css_acc_fw *fw); - void sh_css_binary_args_reset(struct sh_css_binary_args *args); diff --git a/drivers/staging/media/atomisp/pci/sh_css_legacy.h b/drivers/staging/media/atomisp/pci/sh_css_legacy.h index 567c8d6dcc2c..cdf239b070a8 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_legacy.h +++ b/drivers/staging/media/atomisp/pci/sh_css_legacy.h @@ -32,7 +32,6 @@ enum ia_css_pipe_id { IA_CSS_PIPE_ID_VIDEO, IA_CSS_PIPE_ID_CAPTURE, IA_CSS_PIPE_ID_YUVPP, - IA_CSS_PIPE_ID_ACC, IA_CSS_PIPE_ID_NUM }; diff --git a/drivers/staging/media/atomisp/pci/sh_css_param_shading.c b/drivers/staging/media/atomisp/pci/sh_css_param_shading.c index 41a4c9162319..5b43cc656269 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_param_shading.c +++ b/drivers/staging/media/atomisp/pci/sh_css_param_shading.c @@ -13,6 +13,7 @@ * more details. */ +#include <linux/math.h> #include <linux/slab.h> #include <math_support.h> @@ -239,10 +240,9 @@ prepare_shading_table(const struct ia_css_shading_table *in_table, { unsigned int input_width, input_height, table_width, table_height, i; unsigned int left_padding, top_padding, left_cropping; - unsigned int bds_numerator, bds_denominator; - int right_padding; - struct ia_css_shading_table *result; + struct u32_fract bds; + int right_padding; assert(target_table); assert(binary); @@ -265,17 +265,16 @@ prepare_shading_table(const struct ia_css_shading_table *in_table, left_cropping = (binary->info->sp.pipeline.left_cropping == 0) ? binary->dvs_envelope.width : 2 * ISP_VEC_NELEMS; - sh_css_bds_factor_get_numerator_denominator - (bds_factor, &bds_numerator, &bds_denominator); + sh_css_bds_factor_get_fract(bds_factor, &bds); left_padding = (left_padding + binary->info->sp.pipeline.left_cropping) * - bds_numerator / bds_denominator - + bds.numerator / bds.denominator - binary->info->sp.pipeline.left_cropping; right_padding = (binary->internal_frame_info.res.width - - binary->effective_in_frame_res.width * bds_denominator / - bds_numerator - left_cropping) * bds_numerator / bds_denominator; - top_padding = binary->info->sp.pipeline.top_cropping * bds_numerator / - bds_denominator - + binary->effective_in_frame_res.width * bds.denominator / + bds.numerator - left_cropping) * bds.numerator / bds.denominator; + top_padding = binary->info->sp.pipeline.top_cropping * bds.numerator / + bds.denominator - binary->info->sp.pipeline.top_cropping; /* diff --git a/drivers/staging/media/atomisp/pci/sh_css_params.c b/drivers/staging/media/atomisp/pci/sh_css_params.c index 67915d76a87f..f08564f58242 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_params.c +++ b/drivers/staging/media/atomisp/pci/sh_css_params.c @@ -936,8 +936,8 @@ sh_css_set_black_frame(struct ia_css_stream *stream, assert(raw_black_frame); params = stream->isp_params_configs; - height = raw_black_frame->info.res.height; - width = raw_black_frame->info.padded_width; + height = raw_black_frame->frame_info.res.height; + width = raw_black_frame->frame_info.padded_width; ptr = raw_black_frame->data + raw_black_frame->planes.raw.offset; @@ -1187,16 +1187,15 @@ ia_css_process_zoom_and_motion( const struct sh_css_binary_args *args = &stage->args; const struct ia_css_frame_info *out_infos[IA_CSS_BINARY_MAX_OUTPUT_PORTS] = {NULL}; - if (args->out_frame[0]) - out_infos[0] = &args->out_frame[0]->info; + out_infos[0] = ia_css_frame_get_info(args->out_frame[0]); + info = &stage->firmware->info.isp; ia_css_binary_fill_info(info, false, false, ATOMISP_INPUT_FORMAT_RAW_10, - args->in_frame ? &args->in_frame->info : NULL, + ia_css_frame_get_info(args->in_frame), NULL, out_infos, - args->out_vf_frame ? &args->out_vf_frame->info - : NULL, + ia_css_frame_get_info(args->out_vf_frame), &tmp_binary, NULL, -1, true); @@ -3461,10 +3460,10 @@ sh_css_params_write_to_ddr_internal( if (stage->args.delay_frames[0]) { /*When delay frames are present(as in case of video), they are used for dvs. Configure DVS using those params*/ - dvs_in_frame_info = &stage->args.delay_frames[0]->info; + dvs_in_frame_info = &stage->args.delay_frames[0]->frame_info; } else { /*Otherwise, use input frame to configure DVS*/ - dvs_in_frame_info = &stage->args.in_frame->info; + dvs_in_frame_info = &stage->args.in_frame->frame_info; } /* Generate default DVS unity table on start up*/ diff --git a/drivers/staging/media/atomisp/pci/sh_css_sp.c b/drivers/staging/media/atomisp/pci/sh_css_sp.c index 615500a7d3c4..0dd58a7fe2cc 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_sp.c +++ b/drivers/staging/media/atomisp/pci/sh_css_sp.c @@ -277,10 +277,10 @@ sh_css_sp_start_raw_copy(struct ia_css_frame *out_frame, ia_css_pipeline_get_sp_thread_id(pipe_num, &thread_id); pipe = &sh_css_sp_group.pipe[thread_id]; - pipe->copy.raw.height = out_frame->info.res.height; - pipe->copy.raw.width = out_frame->info.res.width; - pipe->copy.raw.padded_width = out_frame->info.padded_width; - pipe->copy.raw.raw_bit_depth = out_frame->info.raw_bit_depth; + pipe->copy.raw.height = out_frame->frame_info.res.height; + pipe->copy.raw.width = out_frame->frame_info.res.width; + pipe->copy.raw.padded_width = out_frame->frame_info.padded_width; + pipe->copy.raw.raw_bit_depth = out_frame->frame_info.raw_bit_depth; pipe->copy.raw.max_input_width = max_input_width; pipe->num_stages = 1; pipe->pipe_id = pipe_id; @@ -351,10 +351,10 @@ sh_css_sp_start_isys_copy(struct ia_css_frame *out_frame, ia_css_pipeline_get_sp_thread_id(pipe_num, &thread_id); pipe = &sh_css_sp_group.pipe[thread_id]; - pipe->copy.raw.height = out_frame->info.res.height; - pipe->copy.raw.width = out_frame->info.res.width; - pipe->copy.raw.padded_width = out_frame->info.padded_width; - pipe->copy.raw.raw_bit_depth = out_frame->info.raw_bit_depth; + pipe->copy.raw.height = out_frame->frame_info.res.height; + pipe->copy.raw.width = out_frame->frame_info.res.width; + pipe->copy.raw.padded_width = out_frame->frame_info.padded_width; + pipe->copy.raw.raw_bit_depth = out_frame->frame_info.raw_bit_depth; pipe->copy.raw.max_input_width = max_input_width; pipe->num_stages = 1; pipe->pipe_id = pipe_id; @@ -451,9 +451,9 @@ sh_css_copy_frame_to_spframe(struct ia_css_frame_sp *sp_frame_out, frame_in->data, frame_in->buf_type); - ia_css_frame_info_to_frame_sp_info(&sp_frame_out->info, &frame_in->info); + ia_css_frame_info_to_frame_sp_info(&sp_frame_out->info, &frame_in->frame_info); - switch (frame_in->info.format) { + switch (frame_in->frame_info.format) { case IA_CSS_FRAME_FORMAT_RAW_PACKED: case IA_CSS_FRAME_FORMAT_RAW: sp_frame_out->planes.raw.offset = frame_in->planes.raw.offset; @@ -536,7 +536,7 @@ set_input_frame_buffer(const struct ia_css_frame *frame) if (!frame) return -EINVAL; - switch (frame->info.format) { + switch (frame->frame_info.format) { case IA_CSS_FRAME_FORMAT_QPLANE6: case IA_CSS_FRAME_FORMAT_YUV420_16: case IA_CSS_FRAME_FORMAT_RAW_PACKED: @@ -567,7 +567,7 @@ set_output_frame_buffer(const struct ia_css_frame *frame, if (!frame) return -EINVAL; - switch (frame->info.format) { + switch (frame->frame_info.format) { case IA_CSS_FRAME_FORMAT_YUV420: case IA_CSS_FRAME_FORMAT_YUV422: case IA_CSS_FRAME_FORMAT_YUV444: @@ -608,7 +608,7 @@ set_view_finder_buffer(const struct ia_css_frame *frame) if (!frame) return -EINVAL; - switch (frame->info.format) { + switch (frame->frame_info.format) { /* the dual output pin */ case IA_CSS_FRAME_FORMAT_NV12: case IA_CSS_FRAME_FORMAT_NV12_16: @@ -819,34 +819,35 @@ static int configure_isp_from_args(const struct sh_css_sp_pipeline *pipeline, ret = ia_css_fpn_configure(binary, &binary->in_frame_info); if (ret) return ret; - ret = ia_css_crop_configure(binary, &args->delay_frames[0]->info); + ret = ia_css_crop_configure(binary, ia_css_frame_get_info(args->delay_frames[0])); if (ret) return ret; ret = ia_css_qplane_configure(pipeline, binary, &binary->in_frame_info); if (ret) return ret; - ret = ia_css_output0_configure(binary, &args->out_frame[0]->info); + ret = ia_css_output0_configure(binary, ia_css_frame_get_info(args->out_frame[0])); if (ret) return ret; - ret = ia_css_output1_configure(binary, &args->out_vf_frame->info); + ret = ia_css_output1_configure(binary, ia_css_frame_get_info(args->out_vf_frame)); if (ret) return ret; ret = ia_css_copy_output_configure(binary, args->copy_output); if (ret) return ret; - ret = ia_css_output0_configure(binary, &args->out_frame[0]->info); + ret = ia_css_output0_configure(binary, ia_css_frame_get_info(args->out_frame[0])); if (ret) return ret; - ret = ia_css_iterator_configure(binary, &args->in_frame->info); + ret = ia_css_iterator_configure(binary, ia_css_frame_get_info(args->in_frame)); if (ret) return ret; - ret = ia_css_dvs_configure(binary, &args->out_frame[0]->info); + ret = ia_css_dvs_configure(binary, ia_css_frame_get_info(args->out_frame[0])); if (ret) return ret; - ret = ia_css_output_configure(binary, &args->out_frame[0]->info); + ret = ia_css_output_configure(binary, ia_css_frame_get_info(args->out_frame[0])); if (ret) return ret; - ret = ia_css_raw_configure(pipeline, binary, &args->in_frame->info, &binary->in_frame_info, two_ppc, deinterleaved); + ret = ia_css_raw_configure(pipeline, binary, ia_css_frame_get_info(args->in_frame), + &binary->in_frame_info, two_ppc, deinterleaved); if (ret) return ret; @@ -1037,7 +1038,7 @@ sh_css_sp_init_stage(struct ia_css_binary *binary, return -EINVAL; if (args->in_frame) - ia_css_get_crop_offsets(pipe, &args->in_frame->info); + ia_css_get_crop_offsets(pipe, &args->in_frame->frame_info); else ia_css_get_crop_offsets(pipe, &binary->in_frame_info); #else @@ -1124,15 +1125,14 @@ sp_init_stage(struct ia_css_pipeline_stage *stage, const struct ia_css_frame_info *out_infos[IA_CSS_BINARY_MAX_OUTPUT_PORTS] = {NULL}; if (args->out_frame[0]) - out_infos[0] = &args->out_frame[0]->info; + out_infos[0] = &args->out_frame[0]->frame_info; info = &firmware->info.isp; ia_css_binary_fill_info(info, false, false, ATOMISP_INPUT_FORMAT_RAW_10, - args->in_frame ? &args->in_frame->info : NULL, + ia_css_frame_get_info(args->in_frame), NULL, out_infos, - args->out_vf_frame ? &args->out_vf_frame->info - : NULL, + ia_css_frame_get_info(args->out_vf_frame), &tmp_binary, NULL, -1, true); @@ -1266,7 +1266,7 @@ sh_css_sp_init_pipeline(struct ia_css_pipeline *me, sh_css_sp_group.pipe[thread_id].thread_id = thread_id; sh_css_sp_group.pipe[thread_id].pipe_num = pipe_num; sh_css_sp_group.pipe[thread_id].num_execs = me->num_execs; - sh_css_sp_group.pipe[thread_id].pipe_qos_config = me->pipe_qos_config; + sh_css_sp_group.pipe[thread_id].pipe_qos_config = QOS_INVALID; sh_css_sp_group.pipe[thread_id].required_bds_factor = required_bds_factor; sh_css_sp_group.pipe[thread_id].input_system_mode = (uint32_t)input_mode; diff --git a/drivers/staging/media/deprecated/atmel/Kconfig b/drivers/staging/media/deprecated/atmel/Kconfig new file mode 100644 index 000000000000..418841ea5a0d --- /dev/null +++ b/drivers/staging/media/deprecated/atmel/Kconfig @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Atmel media platform drivers" + +config VIDEO_ATMEL_ISC + tristate "ATMEL Image Sensor Controller (ISC) support (DEPRECATED)" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + depends on !VIDEO_MICROCHIP_ISC_BASE || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_ATMEL_ISC_BASE + help + This module makes the ATMEL Image Sensor Controller available + as a v4l2 device. + + This driver is deprecated and is scheduled for removal by + the beginning of 2026. See the TODO file for more information. + +config VIDEO_ATMEL_XISC + tristate "ATMEL eXtended Image Sensor Controller (XISC) support (DEPRECATED)" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + depends on !VIDEO_MICROCHIP_ISC_BASE || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_ATMEL_ISC_BASE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This module makes the ATMEL eXtended Image Sensor Controller + available as a v4l2 device. + + This driver is deprecated and is scheduled for removal by + the beginning of 2026. See the TODO file for more information. + +config VIDEO_ATMEL_ISC_BASE + tristate + default n + help + ATMEL ISC and XISC common code base. diff --git a/drivers/staging/media/deprecated/atmel/Makefile b/drivers/staging/media/deprecated/atmel/Makefile new file mode 100644 index 000000000000..34eaeeac5bba --- /dev/null +++ b/drivers/staging/media/deprecated/atmel/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +atmel-isc-objs = atmel-sama5d2-isc.o +atmel-xisc-objs = atmel-sama7g5-isc.o +atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o + +obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o +obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o +obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o diff --git a/drivers/staging/media/deprecated/atmel/TODO b/drivers/staging/media/deprecated/atmel/TODO new file mode 100644 index 000000000000..71691df07a80 --- /dev/null +++ b/drivers/staging/media/deprecated/atmel/TODO @@ -0,0 +1,34 @@ +The Atmel ISC driver is not compliant with media controller specification. +In order to evolve this driver, it has to move to media controller, to +support enhanced features and future products which embed it. +The move to media controller involves several changes which are +not backwards compatible with the current usability of the driver. + +The best example is the way the format is propagated from the top video +driver /dev/videoX down to the sensor. + +In a simple configuration sensor ==> isc , the isc just calls subdev s_fmt +and controls the sensor directly. This is achieved by having a lot of code +inside the driver that will query the subdev at probe time and make a list +of formats which are usable. +Basically the user has nothing to configure, as the isc will handle +everything at the top level. This is an easy way to capture, but also comes +with the drawback of lack of flexibility. +In a more complicated pipeline +sensor ==> controller 1 ==> controller 2 ==> isc +this will not be achievable, as controller 1 and controller 2 might be +media-controller configurable, and will not propagate the formats down to +the sensor. + +After discussions with the media maintainers, the decision is to move +Atmel ISC to staging as-is, to keep the Kconfig symbols and the users +to the driver in staging. Thus, all the existing users of the non +media-controller paradigm will continue to be happy and use the old config +way. + +The new driver was added in the media subsystem with a different +symbol, with the conversion to media controller done, and new users +of the driver will be able to use all the new features. + +The replacement driver is named VIDEO_MICROCHIP_ISC or +VIDEO_MICROCHIP_XISC depending on the product flavor. diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/staging/media/deprecated/atmel/atmel-isc-base.c index 9e5317a7d516..99e61bbfc9bc 100644 --- a/drivers/media/platform/atmel/atmel-isc-base.c +++ b/drivers/staging/media/deprecated/atmel/atmel-isc-base.c @@ -1221,7 +1221,7 @@ static const struct v4l2_file_operations isc_fops = { .poll = vb2_fop_poll, }; -irqreturn_t isc_interrupt(int irq, void *dev_id) +irqreturn_t atmel_isc_interrupt(int irq, void *dev_id) { struct isc_device *isc = (struct isc_device *)dev_id; struct regmap *regmap = isc->regmap; @@ -1267,7 +1267,7 @@ irqreturn_t isc_interrupt(int irq, void *dev_id) return ret; } -EXPORT_SYMBOL_GPL(isc_interrupt); +EXPORT_SYMBOL_GPL(atmel_isc_interrupt); static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) { @@ -1934,14 +1934,14 @@ isc_async_complete_err: return ret; } -const struct v4l2_async_notifier_operations isc_async_ops = { +const struct v4l2_async_notifier_operations atmel_isc_async_ops = { .bound = isc_async_bound, .unbind = isc_async_unbind, .complete = isc_async_complete, }; -EXPORT_SYMBOL_GPL(isc_async_ops); +EXPORT_SYMBOL_GPL(atmel_isc_async_ops); -void isc_subdev_cleanup(struct isc_device *isc) +void atmel_isc_subdev_cleanup(struct isc_device *isc) { struct isc_subdev_entity *subdev_entity; @@ -1952,9 +1952,9 @@ void isc_subdev_cleanup(struct isc_device *isc) INIT_LIST_HEAD(&isc->subdev_entities); } -EXPORT_SYMBOL_GPL(isc_subdev_cleanup); +EXPORT_SYMBOL_GPL(atmel_isc_subdev_cleanup); -int isc_pipeline_init(struct isc_device *isc) +int atmel_isc_pipeline_init(struct isc_device *isc) { struct device *dev = isc->dev; struct regmap *regmap = isc->regmap; @@ -1993,17 +1993,17 @@ int isc_pipeline_init(struct isc_device *isc) return 0; } -EXPORT_SYMBOL_GPL(isc_pipeline_init); +EXPORT_SYMBOL_GPL(atmel_isc_pipeline_init); /* regmap configuration */ #define ATMEL_ISC_REG_MAX 0xd5c -const struct regmap_config isc_regmap_config = { +const struct regmap_config atmel_isc_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = ATMEL_ISC_REG_MAX, }; -EXPORT_SYMBOL_GPL(isc_regmap_config); +EXPORT_SYMBOL_GPL(atmel_isc_regmap_config); MODULE_AUTHOR("Songjun Wu"); MODULE_AUTHOR("Eugen Hristev"); diff --git a/drivers/media/platform/atmel/atmel-isc-clk.c b/drivers/staging/media/deprecated/atmel/atmel-isc-clk.c index 2059fe376b00..d442b5f4c931 100644 --- a/drivers/media/platform/atmel/atmel-isc-clk.c +++ b/drivers/staging/media/deprecated/atmel/atmel-isc-clk.c @@ -277,7 +277,7 @@ static int isc_clk_register(struct isc_device *isc, unsigned int id) return 0; } -int isc_clk_init(struct isc_device *isc) +int atmel_isc_clk_init(struct isc_device *isc) { unsigned int i; int ret; @@ -293,9 +293,9 @@ int isc_clk_init(struct isc_device *isc) return 0; } -EXPORT_SYMBOL_GPL(isc_clk_init); +EXPORT_SYMBOL_GPL(atmel_isc_clk_init); -void isc_clk_cleanup(struct isc_device *isc) +void atmel_isc_clk_cleanup(struct isc_device *isc) { unsigned int i; @@ -308,4 +308,4 @@ void isc_clk_cleanup(struct isc_device *isc) clk_unregister(isc_clk->clk); } } -EXPORT_SYMBOL_GPL(isc_clk_cleanup); +EXPORT_SYMBOL_GPL(atmel_isc_clk_cleanup); diff --git a/drivers/media/platform/atmel/atmel-isc-regs.h b/drivers/staging/media/deprecated/atmel/atmel-isc-regs.h index d06b72228d4f..d06b72228d4f 100644 --- a/drivers/media/platform/atmel/atmel-isc-regs.h +++ b/drivers/staging/media/deprecated/atmel/atmel-isc-regs.h diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/staging/media/deprecated/atmel/atmel-isc.h index ff60ba020cb9..dfc030b5a08f 100644 --- a/drivers/media/platform/atmel/atmel-isc.h +++ b/drivers/staging/media/deprecated/atmel/atmel-isc.h @@ -350,13 +350,13 @@ struct isc_device { u32 formats_list_size; }; -extern const struct regmap_config isc_regmap_config; -extern const struct v4l2_async_notifier_operations isc_async_ops; - -irqreturn_t isc_interrupt(int irq, void *dev_id); -int isc_pipeline_init(struct isc_device *isc); -int isc_clk_init(struct isc_device *isc); -void isc_subdev_cleanup(struct isc_device *isc); -void isc_clk_cleanup(struct isc_device *isc); +extern const struct regmap_config atmel_isc_regmap_config; +extern const struct v4l2_async_notifier_operations atmel_isc_async_ops; + +irqreturn_t atmel_isc_interrupt(int irq, void *dev_id); +int atmel_isc_pipeline_init(struct isc_device *isc); +int atmel_isc_clk_init(struct isc_device *isc); +void atmel_isc_subdev_cleanup(struct isc_device *isc); +void atmel_isc_clk_cleanup(struct isc_device *isc); #endif diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/staging/media/deprecated/atmel/atmel-sama5d2-isc.c index 9881d89a645b..ba0614f981a2 100644 --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c +++ b/drivers/staging/media/deprecated/atmel/atmel-sama5d2-isc.c @@ -408,7 +408,7 @@ static int atmel_isc_probe(struct platform_device *pdev) if (IS_ERR(io_base)) return PTR_ERR(io_base); - isc->regmap = devm_regmap_init_mmio(dev, io_base, &isc_regmap_config); + isc->regmap = devm_regmap_init_mmio(dev, io_base, &atmel_isc_regmap_config); if (IS_ERR(isc->regmap)) { ret = PTR_ERR(isc->regmap); dev_err(dev, "failed to init register map: %d\n", ret); @@ -419,7 +419,7 @@ static int atmel_isc_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_irq(dev, irq, isc_interrupt, 0, + ret = devm_request_irq(dev, irq, atmel_isc_interrupt, 0, "atmel-sama5d2-isc", isc); if (ret < 0) { dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", @@ -464,7 +464,7 @@ static int atmel_isc_probe(struct platform_device *pdev) /* sama5d2-isc : ISPCK is required and mandatory */ isc->ispck_required = true; - ret = isc_pipeline_init(isc); + ret = atmel_isc_pipeline_init(isc); if (ret) return ret; @@ -481,7 +481,7 @@ static int atmel_isc_probe(struct platform_device *pdev) return ret; } - ret = isc_clk_init(isc); + ret = atmel_isc_clk_init(isc); if (ret) { dev_err(dev, "failed to init isc clock: %d\n", ret); goto unprepare_hclk; @@ -523,7 +523,7 @@ static int atmel_isc_probe(struct platform_device *pdev) goto cleanup_subdev; } - subdev_entity->notifier.ops = &isc_async_ops; + subdev_entity->notifier.ops = &atmel_isc_async_ops; ret = v4l2_async_nf_register(&isc->v4l2_dev, &subdev_entity->notifier); @@ -567,7 +567,7 @@ disable_pm: pm_runtime_disable(dev); cleanup_subdev: - isc_subdev_cleanup(isc); + atmel_isc_subdev_cleanup(isc); unregister_v4l2_device: v4l2_device_unregister(&isc->v4l2_dev); @@ -575,7 +575,7 @@ unregister_v4l2_device: unprepare_hclk: clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + atmel_isc_clk_cleanup(isc); return ret; } @@ -586,14 +586,14 @@ static int atmel_isc_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); - isc_subdev_cleanup(isc); + atmel_isc_subdev_cleanup(isc); v4l2_device_unregister(&isc->v4l2_dev); clk_disable_unprepare(isc->ispck); clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + atmel_isc_clk_cleanup(isc); return 0; } diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/staging/media/deprecated/atmel/atmel-sama7g5-isc.c index 8b11aa8340d7..01ababdfcbd9 100644 --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c +++ b/drivers/staging/media/deprecated/atmel/atmel-sama7g5-isc.c @@ -397,7 +397,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) if (IS_ERR(io_base)) return PTR_ERR(io_base); - isc->regmap = devm_regmap_init_mmio(dev, io_base, &isc_regmap_config); + isc->regmap = devm_regmap_init_mmio(dev, io_base, &atmel_isc_regmap_config); if (IS_ERR(isc->regmap)) { ret = PTR_ERR(isc->regmap); dev_err(dev, "failed to init register map: %d\n", ret); @@ -408,7 +408,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_irq(dev, irq, isc_interrupt, 0, + ret = devm_request_irq(dev, irq, atmel_isc_interrupt, 0, "microchip-sama7g5-xisc", isc); if (ret < 0) { dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", @@ -453,7 +453,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) /* sama7g5-isc : ISPCK does not exist, ISC is clocked by MCK */ isc->ispck_required = false; - ret = isc_pipeline_init(isc); + ret = atmel_isc_pipeline_init(isc); if (ret) return ret; @@ -470,7 +470,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) return ret; } - ret = isc_clk_init(isc); + ret = atmel_isc_clk_init(isc); if (ret) { dev_err(dev, "failed to init isc clock: %d\n", ret); goto unprepare_hclk; @@ -513,7 +513,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) goto cleanup_subdev; } - subdev_entity->notifier.ops = &isc_async_ops; + subdev_entity->notifier.ops = &atmel_isc_async_ops; ret = v4l2_async_nf_register(&isc->v4l2_dev, &subdev_entity->notifier); @@ -536,7 +536,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) return 0; cleanup_subdev: - isc_subdev_cleanup(isc); + atmel_isc_subdev_cleanup(isc); unregister_v4l2_device: v4l2_device_unregister(&isc->v4l2_dev); @@ -544,7 +544,7 @@ unregister_v4l2_device: unprepare_hclk: clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + atmel_isc_clk_cleanup(isc); return ret; } @@ -555,13 +555,13 @@ static int microchip_xisc_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); - isc_subdev_cleanup(isc); + atmel_isc_subdev_cleanup(isc); v4l2_device_unregister(&isc->v4l2_dev); clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + atmel_isc_clk_cleanup(isc); return 0; } diff --git a/drivers/staging/media/deprecated/stkwebcam/Kconfig b/drivers/staging/media/deprecated/stkwebcam/Kconfig index 4450403dff41..7234498e634a 100644 --- a/drivers/staging/media/deprecated/stkwebcam/Kconfig +++ b/drivers/staging/media/deprecated/stkwebcam/Kconfig @@ -2,7 +2,7 @@ config VIDEO_STKWEBCAM tristate "USB Syntek DC1125 Camera support (DEPRECATED)" depends on VIDEO_DEV - depends on USB + depends on MEDIA_USB_SUPPORT && MEDIA_CAMERA_SUPPORT help Say Y here if you want to use this type of camera. Supported devices are typically found in some Asus laptops, diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig index 0bacac302d7e..21fd79515042 100644 --- a/drivers/staging/media/imx/Kconfig +++ b/drivers/staging/media/imx/Kconfig @@ -23,12 +23,15 @@ config VIDEO_IMX_CSI default y help A video4linux camera sensor interface driver for i.MX5/6. - -config VIDEO_IMX7_CSI - tristate "i.MX6UL/L / i.MX7 / i.MX8M Camera Sensor Interface driver" - default y - help - Enable support for video4linux camera sensor interface driver for - i.MX6UL/L, i.MX7 or i.MX8M. endmenu endif + +config VIDEO_IMX8MQ_MIPI_CSI2 + tristate "NXP i.MX8MQ MIPI CSI-2 receiver" + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + V4L2 driver for the MIPI CSI-2 receiver found in the i.MX8MQ SoC. diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile index d82be898145b..906a422aa656 100644 --- a/drivers/staging/media/imx/Makefile +++ b/drivers/staging/media/imx/Makefile @@ -14,5 +14,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-media.o obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-media-csi.o obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o -obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o -obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o +obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o diff --git a/drivers/staging/media/imx/TODO b/drivers/staging/media/imx/TODO index 5d3a337c8702..11c9e10d34ae 100644 --- a/drivers/staging/media/imx/TODO +++ b/drivers/staging/media/imx/TODO @@ -2,18 +2,6 @@ - The Frame Interval Monitor could be exported to v4l2-core for general use. -- The CSI subdevice parses its nearest upstream neighbor's device-tree - bus config in order to setup the CSI. Laurent Pinchart argues that - instead the CSI subdev should call its neighbor's g_mbus_config op - (which should be propagated if necessary) to get this info. However - Hans Verkuil is planning to remove the g_mbus_config op. For now this - driver uses the parsed DT bus config method until this issue is - resolved. - - 2020-06: g_mbus has been removed in favour of the get_mbus_config pad - operation which should be used to avoid parsing the remote endpoint - configuration. - - This media driver supports inheriting V4L2 controls to the video capture devices, from the subdevices in the capture device's pipeline. The controls for each capture device are updated in the @@ -23,32 +11,3 @@ - Similarly to the legacy control handling, legacy format handling where formats on the video nodes are influenced by the active format of the connected subdev should be removed. - -- i.MX7: all of the above, since it uses the imx media core - -- i.MX7: use Frame Interval Monitor - -- imx7-media-csi: Restrict the supported formats list to the SoC version. - - The imx7 CSI bridge can be configured to sample pixel components from the Rx - queue in single (8bpp) or double (16bpp) component modes. Image format - variants with different sample sizes (ie YUYV_2X8 vs YUYV_1X16) determine the - pixel components sampling size per each clock cycle and their packing mode - (see imx7_csi_configure() for details). - - As the imx7 CSI bridge can be interfaced with different IP blocks depending on - the SoC model it is integrated on, the Rx queue sampling size should match - the size of the samples transferred by the transmitting IP block. - - To avoid mis-configurations of the capture pipeline, the enumeration of the - supported formats should be restricted to match the pixel source transmitting - mode. - - Example: i.MX8MM SoC integrates the CSI bridge with the Samsung CSIS CSI-2 - receiver which operates in dual pixel sampling mode. The CSI bridge should - only expose the 1X16 formats variant which instructs it to operate in dual - pixel sampling mode. When the CSI bridge is instead integrated on an i.MX7, - which supports both serial and parallel input, it should expose both variants. - - This currently only applies to YUYV formats, but other formats might need - to be handled in the same way. diff --git a/drivers/staging/media/imx/imx-media-csi.c b/drivers/staging/media/imx/imx-media-csi.c index b2b1f4dd41d7..5c3cc7de209d 100644 --- a/drivers/staging/media/imx/imx-media-csi.c +++ b/drivers/staging/media/imx/imx-media-csi.c @@ -97,8 +97,8 @@ struct csi_priv { /* the mipi virtual channel number at link validate */ int vc_num; - /* the upstream endpoint CSI is receiving from */ - struct v4l2_fwnode_endpoint upstream_ep; + /* media bus config of the upstream subdevice CSI is receiving from */ + struct v4l2_mbus_config mbus_cfg; spinlock_t irqlock; /* protect eof_irq handler */ struct timer_list eof_timeout_timer; @@ -125,14 +125,14 @@ static inline struct csi_priv *notifier_to_dev(struct v4l2_async_notifier *n) return container_of(n, struct csi_priv, notifier); } -static inline bool is_parallel_bus(struct v4l2_fwnode_endpoint *ep) +static inline bool is_parallel_bus(struct v4l2_mbus_config *mbus_cfg) { - return ep->bus_type != V4L2_MBUS_CSI2_DPHY; + return mbus_cfg->type != V4L2_MBUS_CSI2_DPHY; } -static inline bool is_parallel_16bit_bus(struct v4l2_fwnode_endpoint *ep) +static inline bool is_parallel_16bit_bus(struct v4l2_mbus_config *mbus_cfg) { - return is_parallel_bus(ep) && ep->bus.parallel.bus_width >= 16; + return is_parallel_bus(mbus_cfg) && mbus_cfg->bus.parallel.bus_width >= 16; } /* @@ -145,36 +145,31 @@ static inline bool is_parallel_16bit_bus(struct v4l2_fwnode_endpoint *ep) * - the CSI is receiving from an 8-bit parallel bus and the incoming * media bus format is other than UYVY8_2X8/YUYV8_2X8. */ -static inline bool requires_passthrough(struct v4l2_fwnode_endpoint *ep, +static inline bool requires_passthrough(struct v4l2_mbus_config *mbus_cfg, struct v4l2_mbus_framefmt *infmt, const struct imx_media_pixfmt *incc) { - if (ep->bus_type == V4L2_MBUS_BT656) // including BT.1120 + if (mbus_cfg->type == V4L2_MBUS_BT656) // including BT.1120 return false; - return incc->bayer || is_parallel_16bit_bus(ep) || - (is_parallel_bus(ep) && + return incc->bayer || is_parallel_16bit_bus(mbus_cfg) || + (is_parallel_bus(mbus_cfg) && infmt->code != MEDIA_BUS_FMT_UYVY8_2X8 && infmt->code != MEDIA_BUS_FMT_YUYV8_2X8); } /* - * Parses the fwnode endpoint from the source pad of the entity - * connected to this CSI. This will either be the entity directly - * upstream from the CSI-2 receiver, directly upstream from the - * video mux, or directly upstream from the CSI itself. The endpoint - * is needed to determine the bus type and bus config coming into - * the CSI. + * Queries the media bus config of the upstream entity that provides data to + * the CSI. This will either be the entity directly upstream from the CSI-2 + * receiver, directly upstream from a video mux, or directly upstream from + * the CSI itself. */ -static int csi_get_upstream_endpoint(struct csi_priv *priv, - struct v4l2_fwnode_endpoint *ep) +static int csi_get_upstream_mbus_config(struct csi_priv *priv, + struct v4l2_mbus_config *mbus_cfg) { - struct fwnode_handle *endpoint; - struct v4l2_subdev *sd; - struct media_pad *pad; - - if (!IS_ENABLED(CONFIG_OF)) - return -ENXIO; + struct v4l2_subdev *sd, *remote_sd; + struct media_pad *remote_pad; + int ret; if (!priv->src_sd) return -EPIPE; @@ -206,19 +201,21 @@ static int csi_get_upstream_endpoint(struct csi_priv *priv, } /* get source pad of entity directly upstream from sd */ - pad = imx_media_pipeline_pad(&sd->entity, 0, 0, true); - if (!pad) - return -ENODEV; - - endpoint = imx_media_get_pad_fwnode(pad); - if (IS_ERR(endpoint)) - return PTR_ERR(endpoint); + remote_pad = media_entity_remote_pad_unique(&sd->entity, + MEDIA_PAD_FL_SOURCE); + if (IS_ERR(remote_pad)) + return PTR_ERR(remote_pad); - v4l2_fwnode_endpoint_parse(endpoint, ep); + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); - fwnode_handle_put(endpoint); + ret = v4l2_subdev_call(remote_sd, pad, get_mbus_config, + remote_pad->index, mbus_cfg); + if (ret == -ENOIOCTLCMD) + v4l2_err(&priv->sd, + "entity %s does not implement get_mbus_config()\n", + remote_pad->entity->name); - return 0; + return ret; } static void csi_idmac_put_ipu_resources(struct csi_priv *priv) @@ -435,7 +432,7 @@ static int csi_idmac_setup_channel(struct csi_priv *priv) image.phys0 = phys[0]; image.phys1 = phys[1]; - passthrough = requires_passthrough(&priv->upstream_ep, infmt, incc); + passthrough = requires_passthrough(&priv->mbus_cfg, infmt, incc); passthrough_cycles = 1; /* @@ -708,7 +705,6 @@ static int csi_setup(struct csi_priv *priv) { struct v4l2_mbus_framefmt *infmt, *outfmt; const struct imx_media_pixfmt *incc; - struct v4l2_mbus_config mbus_cfg; struct v4l2_mbus_framefmt if_fmt; struct v4l2_rect crop; @@ -716,13 +712,6 @@ static int csi_setup(struct csi_priv *priv) incc = priv->cc[CSI_SINK_PAD]; outfmt = &priv->format_mbus[priv->active_output_pad]; - /* compose mbus_config from the upstream endpoint */ - mbus_cfg.type = priv->upstream_ep.bus_type; - if (is_parallel_bus(&priv->upstream_ep)) - mbus_cfg.bus.parallel = priv->upstream_ep.bus.parallel; - else - mbus_cfg.bus.mipi_csi2 = priv->upstream_ep.bus.mipi_csi2; - if_fmt = *infmt; crop = priv->crop; @@ -730,7 +719,7 @@ static int csi_setup(struct csi_priv *priv) * if cycles is set, we need to handle this over multiple cycles as * generic/bayer data */ - if (is_parallel_bus(&priv->upstream_ep) && incc->cycles) { + if (is_parallel_bus(&priv->mbus_cfg) && incc->cycles) { if_fmt.width *= incc->cycles; crop.width *= incc->cycles; } @@ -741,7 +730,7 @@ static int csi_setup(struct csi_priv *priv) priv->crop.width == 2 * priv->compose.width, priv->crop.height == 2 * priv->compose.height); - ipu_csi_init_interface(priv->csi, &mbus_cfg, &if_fmt, outfmt); + ipu_csi_init_interface(priv->csi, &priv->mbus_cfg, &if_fmt, outfmt); ipu_csi_set_dest(priv->csi, priv->dest); @@ -769,7 +758,7 @@ static int csi_start(struct csi_priv *priv) return ret; /* Skip first few frames from a BT.656 source */ - if (priv->upstream_ep.bus_type == V4L2_MBUS_BT656) { + if (priv->mbus_cfg.type == V4L2_MBUS_BT656) { u32 delay_usec, bad_frames = 20; delay_usec = DIV_ROUND_UP_ULL((u64)USEC_PER_SEC * @@ -1118,7 +1107,7 @@ static int csi_link_validate(struct v4l2_subdev *sd, struct v4l2_subdev_format *sink_fmt) { struct csi_priv *priv = v4l2_get_subdevdata(sd); - struct v4l2_fwnode_endpoint upstream_ep = { .bus_type = 0 }; + struct v4l2_mbus_config mbus_cfg = { .type = 0 }; bool is_csi2; int ret; @@ -1127,16 +1116,17 @@ static int csi_link_validate(struct v4l2_subdev *sd, if (ret) return ret; - ret = csi_get_upstream_endpoint(priv, &upstream_ep); + ret = csi_get_upstream_mbus_config(priv, &mbus_cfg); if (ret) { - v4l2_err(&priv->sd, "failed to find upstream endpoint\n"); + v4l2_err(&priv->sd, + "failed to get upstream media bus configuration\n"); return ret; } mutex_lock(&priv->lock); - priv->upstream_ep = upstream_ep; - is_csi2 = !is_parallel_bus(&upstream_ep); + priv->mbus_cfg = mbus_cfg; + is_csi2 = !is_parallel_bus(&mbus_cfg); if (is_csi2) { /* * NOTE! It seems the virtual channels from the mipi csi-2 @@ -1192,7 +1182,7 @@ static void csi_try_crop(struct csi_priv *priv, struct v4l2_rect *crop, struct v4l2_subdev_state *sd_state, struct v4l2_mbus_framefmt *infmt, - struct v4l2_fwnode_endpoint *upstream_ep) + struct v4l2_mbus_config *mbus_cfg) { u32 in_height; @@ -1216,7 +1206,7 @@ static void csi_try_crop(struct csi_priv *priv, * sync, so fix it to NTSC/PAL active lines. NTSC contains * 2 extra lines of active video that need to be cropped. */ - if (upstream_ep->bus_type == V4L2_MBUS_BT656 && + if (mbus_cfg->type == V4L2_MBUS_BT656 && (V4L2_FIELD_HAS_BOTH(infmt->field) || infmt->field == V4L2_FIELD_ALTERNATE)) { crop->height = in_height; @@ -1233,7 +1223,7 @@ static int csi_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_mbus_code_enum *code) { struct csi_priv *priv = v4l2_get_subdevdata(sd); - struct v4l2_fwnode_endpoint upstream_ep = { .bus_type = 0 }; + struct v4l2_mbus_config mbus_cfg = { .type = 0 }; const struct imx_media_pixfmt *incc; struct v4l2_mbus_framefmt *infmt; int ret = 0; @@ -1250,13 +1240,14 @@ static int csi_enum_mbus_code(struct v4l2_subdev *sd, break; case CSI_SRC_PAD_DIRECT: case CSI_SRC_PAD_IDMAC: - ret = csi_get_upstream_endpoint(priv, &upstream_ep); + ret = csi_get_upstream_mbus_config(priv, &mbus_cfg); if (ret) { - v4l2_err(&priv->sd, "failed to find upstream endpoint\n"); + v4l2_err(&priv->sd, + "failed to get upstream media bus configuration\n"); goto out; } - if (requires_passthrough(&upstream_ep, infmt, incc)) { + if (requires_passthrough(&mbus_cfg, infmt, incc)) { if (code->index != 0) { ret = -EINVAL; goto out; @@ -1426,7 +1417,7 @@ static void csi_try_field(struct csi_priv *priv, } static void csi_try_fmt(struct csi_priv *priv, - struct v4l2_fwnode_endpoint *upstream_ep, + struct v4l2_mbus_config *mbus_cfg, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat, struct v4l2_rect *crop, @@ -1447,7 +1438,7 @@ static void csi_try_fmt(struct csi_priv *priv, sdformat->format.width = compose->width; sdformat->format.height = compose->height; - if (requires_passthrough(upstream_ep, infmt, incc)) { + if (requires_passthrough(mbus_cfg, infmt, incc)) { sdformat->format.code = infmt->code; *cc = incc; } else { @@ -1497,8 +1488,7 @@ static void csi_try_fmt(struct csi_priv *priv, crop->height = sdformat->format.height; if (sdformat->format.field == V4L2_FIELD_ALTERNATE) crop->height *= 2; - csi_try_crop(priv, crop, sd_state, &sdformat->format, - upstream_ep); + csi_try_crop(priv, crop, sd_state, &sdformat->format, mbus_cfg); compose->left = 0; compose->top = 0; compose->width = crop->width; @@ -1516,7 +1506,7 @@ static int csi_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_format *sdformat) { struct csi_priv *priv = v4l2_get_subdevdata(sd); - struct v4l2_fwnode_endpoint upstream_ep = { .bus_type = 0 }; + struct v4l2_mbus_config mbus_cfg = { .type = 0 }; const struct imx_media_pixfmt *cc; struct v4l2_mbus_framefmt *fmt; struct v4l2_rect *crop, *compose; @@ -1525,9 +1515,10 @@ static int csi_set_fmt(struct v4l2_subdev *sd, if (sdformat->pad >= CSI_NUM_PADS) return -EINVAL; - ret = csi_get_upstream_endpoint(priv, &upstream_ep); + ret = csi_get_upstream_mbus_config(priv, &mbus_cfg); if (ret) { - v4l2_err(&priv->sd, "failed to find upstream endpoint\n"); + v4l2_err(&priv->sd, + "failed to get upstream media bus configuration\n"); return ret; } @@ -1541,8 +1532,7 @@ static int csi_set_fmt(struct v4l2_subdev *sd, crop = __csi_get_crop(priv, sd_state, sdformat->which); compose = __csi_get_compose(priv, sd_state, sdformat->which); - csi_try_fmt(priv, &upstream_ep, sd_state, sdformat, crop, compose, - &cc); + csi_try_fmt(priv, &mbus_cfg, sd_state, sdformat, crop, compose, &cc); fmt = __csi_get_fmt(priv, sd_state, sdformat->pad, sdformat->which); *fmt = sdformat->format; @@ -1559,8 +1549,8 @@ static int csi_set_fmt(struct v4l2_subdev *sd, format.pad = pad; format.which = sdformat->which; format.format = sdformat->format; - csi_try_fmt(priv, &upstream_ep, sd_state, &format, - NULL, compose, &outcc); + csi_try_fmt(priv, &mbus_cfg, sd_state, &format, NULL, + compose, &outcc); outfmt = __csi_get_fmt(priv, sd_state, pad, sdformat->which); @@ -1648,7 +1638,7 @@ static int csi_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_selection *sel) { struct csi_priv *priv = v4l2_get_subdevdata(sd); - struct v4l2_fwnode_endpoint upstream_ep = { .bus_type = 0 }; + struct v4l2_mbus_config mbus_cfg = { .type = 0 }; struct v4l2_mbus_framefmt *infmt; struct v4l2_rect *crop, *compose; int pad, ret; @@ -1656,9 +1646,10 @@ static int csi_set_selection(struct v4l2_subdev *sd, if (sel->pad != CSI_SINK_PAD) return -EINVAL; - ret = csi_get_upstream_endpoint(priv, &upstream_ep); + ret = csi_get_upstream_mbus_config(priv, &mbus_cfg); if (ret) { - v4l2_err(&priv->sd, "failed to find upstream endpoint\n"); + v4l2_err(&priv->sd, + "failed to get upstream media bus configuration\n"); return ret; } @@ -1687,7 +1678,7 @@ static int csi_set_selection(struct v4l2_subdev *sd, goto out; } - csi_try_crop(priv, &sel->r, sd_state, infmt, &upstream_ep); + csi_try_crop(priv, &sel->r, sd_state, infmt, &mbus_cfg); *crop = sel->r; diff --git a/drivers/staging/media/imx/imx-media-fim.c b/drivers/staging/media/imx/imx-media-fim.c index 3a9182933508..fb6590dcfc36 100644 --- a/drivers/staging/media/imx/imx-media-fim.c +++ b/drivers/staging/media/imx/imx-media-fim.c @@ -187,54 +187,6 @@ out_update_ts: send_fim_event(fim, error_avg); } -#ifdef CONFIG_IMX_GPT_ICAP -/* - * Input Capture method of measuring frame intervals. Not subject - * to interrupt latency. - */ -static void fim_input_capture_handler(int channel, void *dev_id, - ktime_t timestamp) -{ - struct imx_media_fim *fim = dev_id; - unsigned long flags; - - spin_lock_irqsave(&fim->lock, flags); - - frame_interval_monitor(fim, timestamp); - - if (!completion_done(&fim->icap_first_event)) - complete(&fim->icap_first_event); - - spin_unlock_irqrestore(&fim->lock, flags); -} - -static int fim_request_input_capture(struct imx_media_fim *fim) -{ - init_completion(&fim->icap_first_event); - - return mxc_request_input_capture(fim->icap_channel, - fim_input_capture_handler, - fim->icap_flags, fim); -} - -static void fim_free_input_capture(struct imx_media_fim *fim) -{ - mxc_free_input_capture(fim->icap_channel, fim); -} - -#else /* CONFIG_IMX_GPT_ICAP */ - -static int fim_request_input_capture(struct imx_media_fim *fim) -{ - return 0; -} - -static void fim_free_input_capture(struct imx_media_fim *fim) -{ -} - -#endif /* CONFIG_IMX_GPT_ICAP */ - /* * In case we are monitoring the first frame interval after streamon * (when fim->num_skip = 0), we need a valid fim->last_ts before we @@ -434,15 +386,8 @@ int imx_media_fim_set_stream(struct imx_media_fim *fim, update_fim_nominal(fim, fi); spin_unlock_irqrestore(&fim->lock, flags); - if (icap_enabled(fim)) { - ret = fim_request_input_capture(fim); - if (ret) - goto out; - fim_acquire_first_ts(fim); - } - } else { if (icap_enabled(fim)) - fim_free_input_capture(fim); + fim_acquire_first_ts(fim); } fim->stream_on = on; diff --git a/drivers/staging/media/imx/imx-media-utils.c b/drivers/staging/media/imx/imx-media-utils.c index 3e7462112649..411e907b68eb 100644 --- a/drivers/staging/media/imx/imx-media-utils.c +++ b/drivers/staging/media/imx/imx-media-utils.c @@ -814,39 +814,6 @@ imx_media_pipeline_video_device(struct media_entity *start_entity, EXPORT_SYMBOL_GPL(imx_media_pipeline_video_device); /* - * Find a fwnode endpoint that maps to the given subdevice's pad. - * If there are multiple endpoints that map to the pad, only the - * first endpoint encountered is returned. - * - * On success the refcount of the returned fwnode endpoint is - * incremented. - */ -struct fwnode_handle *imx_media_get_pad_fwnode(struct media_pad *pad) -{ - struct fwnode_handle *endpoint; - struct v4l2_subdev *sd; - - if (!is_media_entity_v4l2_subdev(pad->entity)) - return ERR_PTR(-ENODEV); - - sd = media_entity_to_v4l2_subdev(pad->entity); - - fwnode_graph_for_each_endpoint(dev_fwnode(sd->dev), endpoint) { - int pad_idx = media_entity_get_fwnode_pad(&sd->entity, - endpoint, - pad->flags); - if (pad_idx < 0) - continue; - - if (pad_idx == pad->index) - return endpoint; - } - - return ERR_PTR(-ENODEV); -} -EXPORT_SYMBOL_GPL(imx_media_get_pad_fwnode); - -/* * Turn current pipeline streaming on/off starting from entity. */ int imx_media_pipeline_set_stream(struct imx_media_dev *imxmd, diff --git a/drivers/staging/media/imx/imx-media.h b/drivers/staging/media/imx/imx-media.h index f263fc3adbb9..f679249d82e4 100644 --- a/drivers/staging/media/imx/imx-media.h +++ b/drivers/staging/media/imx/imx-media.h @@ -219,7 +219,6 @@ imx_media_pipeline_subdev(struct media_entity *start_entity, u32 grp_id, struct video_device * imx_media_pipeline_video_device(struct media_entity *start_entity, enum v4l2_buf_type buftype, bool upstream); -struct fwnode_handle *imx_media_get_pad_fwnode(struct media_pad *pad); struct imx_media_dma_buf { void *virt; diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c index ce13e746c15f..e530767e80a5 100644 --- a/drivers/staging/media/ipu3/ipu3-v4l2.c +++ b/drivers/staging/media/ipu3/ipu3-v4l2.c @@ -188,6 +188,28 @@ static int imgu_subdev_set_fmt(struct v4l2_subdev *sd, return 0; } +static struct v4l2_rect * +imgu_subdev_get_crop(struct imgu_v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&sd->subdev, sd_state, pad); + else + return &sd->rect.eff; +} + +static struct v4l2_rect * +imgu_subdev_get_compose(struct imgu_v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_compose(&sd->subdev, sd_state, pad); + else + return &sd->rect.bds; +} + static int imgu_subdev_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) @@ -200,18 +222,12 @@ static int imgu_subdev_get_selection(struct v4l2_subdev *sd, switch (sel->target) { case V4L2_SEL_TGT_CROP: - if (sel->which == V4L2_SUBDEV_FORMAT_TRY) - sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, - sel->pad); - else - sel->r = imgu_sd->rect.eff; + sel->r = *imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad, + sel->which); return 0; case V4L2_SEL_TGT_COMPOSE: - if (sel->which == V4L2_SUBDEV_FORMAT_TRY) - sel->r = *v4l2_subdev_get_try_compose(sd, sd_state, - sel->pad); - else - sel->r = imgu_sd->rect.bds; + sel->r = *imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad, + sel->which); return 0; default: return -EINVAL; @@ -223,10 +239,9 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_selection *sel) { struct imgu_device *imgu = v4l2_get_subdevdata(sd); - struct imgu_v4l2_subdev *imgu_sd = container_of(sd, - struct imgu_v4l2_subdev, - subdev); - struct v4l2_rect *rect, *try_sel; + struct imgu_v4l2_subdev *imgu_sd = + container_of(sd, struct imgu_v4l2_subdev, subdev); + struct v4l2_rect *rect; dev_dbg(&imgu->pci_dev->dev, "set subdev %u sel which %u target 0x%4x rect [%ux%u]", @@ -238,22 +253,18 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd, switch (sel->target) { case V4L2_SEL_TGT_CROP: - try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); - rect = &imgu_sd->rect.eff; + rect = imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad, + sel->which); break; case V4L2_SEL_TGT_COMPOSE: - try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad); - rect = &imgu_sd->rect.bds; + rect = imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad, + sel->which); break; default: return -EINVAL; } - if (sel->which == V4L2_SUBDEV_FORMAT_TRY) - *try_sel = sel->r; - else - *rect = sel->r; - + *rect = sel->r; return 0; } diff --git a/drivers/staging/media/meson/vdec/codec_vp9.c b/drivers/staging/media/meson/vdec/codec_vp9.c index 897f5d7a6aad..394df5761556 100644 --- a/drivers/staging/media/meson/vdec/codec_vp9.c +++ b/drivers/staging/media/meson/vdec/codec_vp9.c @@ -1459,7 +1459,7 @@ static void vp9_tree_merge_probs(unsigned int *prev_prob, if (den == 0) { new_prob = pre_prob; } else { - m_count = den < MODE_MV_COUNT_SAT ? den : MODE_MV_COUNT_SAT; + m_count = min(den, MODE_MV_COUNT_SAT); get_prob = clip_prob(div_r32(((int64_t)tree_left * 256 + (den >> 1)), @@ -1513,7 +1513,7 @@ static void adapt_coef_probs_cxt(unsigned int *prev_prob, /* get binary prob */ num = branch_ct[node][0]; den = branch_ct[node][0] + branch_ct[node][1]; - m_count = den < count_sat ? den : count_sat; + m_count = min(den, count_sat); get_prob = (den == 0) ? 128u : @@ -1649,8 +1649,7 @@ static void adapt_coef_probs(int prev_kf, int cur_kf, int pre_fc, else if (coef_count_node_start == VP9_MV_BITS_1_COUNT_START) coef_node_start = VP9_MV_BITS_1_START; - else if (coef_count_node_start == - VP9_MV_CLASS0_HP_0_COUNT_START) + else /* node_start == VP9_MV_CLASS0_HP_0_COUNT_START */ coef_node_start = VP9_MV_CLASS0_HP_0_START; den = count[coef_count_node_start] + @@ -1664,8 +1663,7 @@ static void adapt_coef_probs(int prev_kf, int cur_kf, int pre_fc, if (den == 0) { new_prob = pre_prob; } else { - m_count = den < MODE_MV_COUNT_SAT ? - den : MODE_MV_COUNT_SAT; + m_count = min(den, MODE_MV_COUNT_SAT); get_prob = clip_prob(div_r32(((int64_t) count[coef_count_node_start] * 256 + diff --git a/drivers/staging/media/omap4iss/iss_video.c b/drivers/staging/media/omap4iss/iss_video.c index 60f3d84be828..0ad70faa9ba0 100644 --- a/drivers/staging/media/omap4iss/iss_video.c +++ b/drivers/staging/media/omap4iss/iss_video.c @@ -19,8 +19,6 @@ #include <media/v4l2-ioctl.h> #include <media/v4l2-mc.h> -#include <asm/cacheflush.h> - #include "iss_video.h" #include "iss.h" diff --git a/drivers/staging/media/omap4iss/iss_video.h b/drivers/staging/media/omap4iss/iss_video.h index ca2d5edb6261..19668d28b682 100644 --- a/drivers/staging/media/omap4iss/iss_video.h +++ b/drivers/staging/media/omap4iss/iss_video.h @@ -53,19 +53,19 @@ enum iss_pipeline_stream_state { enum iss_pipeline_state { /* The stream has been started on the input video node. */ - ISS_PIPELINE_STREAM_INPUT = 1, + ISS_PIPELINE_STREAM_INPUT = BIT(0), /* The stream has been started on the output video node. */ - ISS_PIPELINE_STREAM_OUTPUT = (1 << 1), + ISS_PIPELINE_STREAM_OUTPUT = BIT(1), /* At least one buffer is queued on the input video node. */ - ISS_PIPELINE_QUEUE_INPUT = (1 << 2), + ISS_PIPELINE_QUEUE_INPUT = BIT(2), /* At least one buffer is queued on the output video node. */ - ISS_PIPELINE_QUEUE_OUTPUT = (1 << 3), + ISS_PIPELINE_QUEUE_OUTPUT = BIT(3), /* The input entity is idle, ready to be started. */ - ISS_PIPELINE_IDLE_INPUT = (1 << 4), + ISS_PIPELINE_IDLE_INPUT = BIT(4), /* The output entity is idle, ready to be started. */ - ISS_PIPELINE_IDLE_OUTPUT = (1 << 5), + ISS_PIPELINE_IDLE_OUTPUT = BIT(5), /* The pipeline is currently streaming. */ - ISS_PIPELINE_STREAM = (1 << 6), + ISS_PIPELINE_STREAM = BIT(6), }; /* @@ -126,9 +126,9 @@ struct iss_buffer { enum iss_video_dmaqueue_flags { /* Set if DMA queue becomes empty when ISS_PIPELINE_STREAM_CONTINUOUS */ - ISS_VIDEO_DMAQUEUE_UNDERRUN = (1 << 0), + ISS_VIDEO_DMAQUEUE_UNDERRUN = BIT(0), /* Set when queuing buffer to an empty DMA queue */ - ISS_VIDEO_DMAQUEUE_QUEUED = (1 << 1), + ISS_VIDEO_DMAQUEUE_QUEUED = BIT(1), }; #define iss_video_dmaqueue_flags_clr(video) \ diff --git a/drivers/staging/media/rkvdec/rkvdec-vp9.c b/drivers/staging/media/rkvdec/rkvdec-vp9.c index d8c1c0db15c7..cfae99b40ccb 100644 --- a/drivers/staging/media/rkvdec/rkvdec-vp9.c +++ b/drivers/staging/media/rkvdec/rkvdec-vp9.c @@ -84,6 +84,8 @@ struct rkvdec_vp9_probs { struct rkvdec_vp9_inter_frame_probs inter; struct rkvdec_vp9_intra_only_frame_probs intra_only; }; + /* 128 bit alignment */ + u8 padding1[11]; }; /* Data structure describing auxiliary buffer format. */ @@ -1006,6 +1008,7 @@ static int rkvdec_vp9_start(struct rkvdec_ctx *ctx) ctx->priv = vp9_ctx; + BUILD_BUG_ON(sizeof(priv_tbl->probs) % 16); /* ensure probs size is 128-bit aligned */ priv_tbl = dma_alloc_coherent(rkvdec->dev, sizeof(*priv_tbl), &vp9_ctx->priv_tbl.dma, GFP_KERNEL); if (!priv_tbl) { diff --git a/drivers/staging/media/sunxi/Kconfig b/drivers/staging/media/sunxi/Kconfig index 4549a135741f..62a486aba88b 100644 --- a/drivers/staging/media/sunxi/Kconfig +++ b/drivers/staging/media/sunxi/Kconfig @@ -12,5 +12,6 @@ config VIDEO_SUNXI if VIDEO_SUNXI source "drivers/staging/media/sunxi/cedrus/Kconfig" +source "drivers/staging/media/sunxi/sun6i-isp/Kconfig" endif diff --git a/drivers/staging/media/sunxi/Makefile b/drivers/staging/media/sunxi/Makefile index b87140b0e15f..3d20b2f0e644 100644 --- a/drivers/staging/media/sunxi/Makefile +++ b/drivers/staging/media/sunxi/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_VIDEO_SUNXI_CEDRUS) += cedrus/ +obj-$(CONFIG_VIDEO_SUN6I_ISP) += sun6i-isp/ diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.c b/drivers/staging/media/sunxi/cedrus/cedrus.c index 55c54dfdc585..a43d5ff66716 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus.c @@ -45,23 +45,37 @@ static int cedrus_try_ctrl(struct v4l2_ctrl *ctrl) } else if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS) { const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps; struct cedrus_ctx *ctx = container_of(ctrl->handler, struct cedrus_ctx, hdl); + unsigned int bit_depth, max_depth; + struct vb2_queue *vq; if (sps->chroma_format_idc != 1) /* Only 4:2:0 is supported */ return -EINVAL; - if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) - /* Luma and chroma bit depth mismatch */ + bit_depth = max(sps->bit_depth_luma_minus8, + sps->bit_depth_chroma_minus8) + 8; + + if (cedrus_is_capable(ctx, CEDRUS_CAPABILITY_H265_10_DEC)) + max_depth = 10; + else + max_depth = 8; + + if (bit_depth > max_depth) return -EINVAL; - if (ctx->dev->capabilities & CEDRUS_CAPABILITY_H265_10_DEC) { - if (sps->bit_depth_luma_minus8 != 0 && sps->bit_depth_luma_minus8 != 2) - /* Only 8-bit and 10-bit are supported */ + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + + /* + * Bit depth can't be higher than currently set once + * buffers are allocated. + */ + if (vb2_is_busy(vq)) { + if (ctx->bit_depth < bit_depth) return -EINVAL; } else { - if (sps->bit_depth_luma_minus8 != 0) - /* Only 8-bit is supported */ - return -EINVAL; + ctx->bit_depth = bit_depth; + cedrus_reset_cap_format(ctx); } } @@ -77,56 +91,56 @@ static const struct cedrus_control cedrus_controls[] = { .cfg = { .id = V4L2_CID_STATELESS_MPEG2_SEQUENCE, }, - .codec = CEDRUS_CODEC_MPEG2, + .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_MPEG2_PICTURE, }, - .codec = CEDRUS_CODEC_MPEG2, + .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_MPEG2_QUANTISATION, }, - .codec = CEDRUS_CODEC_MPEG2, + .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_SLICE_PARAMS, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_SPS, .ops = &cedrus_ctrl_ops, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_PPS, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_PRED_WEIGHTS, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { @@ -134,7 +148,7 @@ static const struct cedrus_control cedrus_controls[] = { .max = V4L2_STATELESS_H264_DECODE_MODE_SLICE_BASED, .def = V4L2_STATELESS_H264_DECODE_MODE_SLICE_BASED, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { @@ -142,7 +156,7 @@ static const struct cedrus_control cedrus_controls[] = { .max = V4L2_STATELESS_H264_START_CODE_NONE, .def = V4L2_STATELESS_H264_START_CODE_NONE, }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, /* * We only expose supported profiles information, @@ -160,20 +174,20 @@ static const struct cedrus_control cedrus_controls[] = { .menu_skip_mask = BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED), }, - .codec = CEDRUS_CODEC_H264, + .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_SPS, .ops = &cedrus_ctrl_ops, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_PPS, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { @@ -181,13 +195,13 @@ static const struct cedrus_control cedrus_controls[] = { /* The driver can only handle 1 entry per slice for now */ .dims = { 1 }, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { @@ -197,7 +211,7 @@ static const struct cedrus_control cedrus_controls[] = { .max = 0xffffffff, .step = 1, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { @@ -205,7 +219,7 @@ static const struct cedrus_control cedrus_controls[] = { .max = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, .def = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { @@ -213,19 +227,19 @@ static const struct cedrus_control cedrus_controls[] = { .max = V4L2_STATELESS_HEVC_START_CODE_NONE, .def = V4L2_STATELESS_HEVC_START_CODE_NONE, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_VP8_FRAME, }, - .codec = CEDRUS_CODEC_VP8, + .capabilities = CEDRUS_CAPABILITY_VP8_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS, }, - .codec = CEDRUS_CODEC_H265, + .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, }; @@ -258,7 +272,7 @@ static int cedrus_init_ctrls(struct cedrus_dev *dev, struct cedrus_ctx *ctx) struct v4l2_ctrl_handler *hdl = &ctx->hdl; struct v4l2_ctrl *ctrl; unsigned int ctrl_size; - unsigned int i; + unsigned int i, j; v4l2_ctrl_handler_init(hdl, CEDRUS_CONTROLS_COUNT); if (hdl->error) { @@ -274,7 +288,11 @@ static int cedrus_init_ctrls(struct cedrus_dev *dev, struct cedrus_ctx *ctx) if (!ctx->ctrls) return -ENOMEM; + j = 0; for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) { + if (!cedrus_is_capable(ctx, cedrus_controls[i].capabilities)) + continue; + ctrl = v4l2_ctrl_new_custom(hdl, &cedrus_controls[i].cfg, NULL); if (hdl->error) { @@ -289,7 +307,7 @@ static int cedrus_init_ctrls(struct cedrus_dev *dev, struct cedrus_ctx *ctx) return hdl->error; } - ctx->ctrls[i] = ctrl; + ctx->ctrls[j++] = ctrl; } ctx->fh.ctrl_handler = hdl; @@ -350,27 +368,20 @@ static int cedrus_open(struct file *file) v4l2_fh_init(&ctx->fh, video_devdata(file)); file->private_data = &ctx->fh; ctx->dev = dev; - - ret = cedrus_init_ctrls(dev, ctx); - if (ret) - goto err_free; + ctx->bit_depth = 8; ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &cedrus_queue_init); if (IS_ERR(ctx->fh.m2m_ctx)) { ret = PTR_ERR(ctx->fh.m2m_ctx); - goto err_ctrls; + goto err_free; } - ctx->dst_fmt.pixelformat = V4L2_PIX_FMT_NV12_32L32; - cedrus_prepare_format(&ctx->dst_fmt); - ctx->src_fmt.pixelformat = V4L2_PIX_FMT_MPEG2_SLICE; - /* - * TILED_NV12 has more strict requirements, so copy the width and - * height to src_fmt to ensure that is matches the dst_fmt resolution. - */ - ctx->src_fmt.width = ctx->dst_fmt.width; - ctx->src_fmt.height = ctx->dst_fmt.height; - cedrus_prepare_format(&ctx->src_fmt); + + cedrus_reset_out_format(ctx); + + ret = cedrus_init_ctrls(dev, ctx); + if (ret) + goto err_m2m_release; v4l2_fh_add(&ctx->fh); @@ -378,8 +389,8 @@ static int cedrus_open(struct file *file) return 0; -err_ctrls: - v4l2_ctrl_handler_free(&ctx->hdl); +err_m2m_release: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); err_free: kfree(ctx); mutex_unlock(&dev->dev_mutex); @@ -460,11 +471,6 @@ static int cedrus_probe(struct platform_device *pdev) return ret; } - dev->dec_ops[CEDRUS_CODEC_MPEG2] = &cedrus_dec_ops_mpeg2; - dev->dec_ops[CEDRUS_CODEC_H264] = &cedrus_dec_ops_h264; - dev->dec_ops[CEDRUS_CODEC_H265] = &cedrus_dec_ops_h265; - dev->dec_ops[CEDRUS_CODEC_VP8] = &cedrus_dec_ops_vp8; - mutex_init(&dev->dev_mutex); INIT_DELAYED_WORK(&dev->watchdog_work, cedrus_watchdog); diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h index 93a2196006f7..522c184e2afc 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus.h +++ b/drivers/staging/media/sunxi/cedrus/cedrus.h @@ -35,14 +35,6 @@ #define CEDRUS_CAPABILITY_VP8_DEC BIT(4) #define CEDRUS_CAPABILITY_H265_10_DEC BIT(5) -enum cedrus_codec { - CEDRUS_CODEC_MPEG2, - CEDRUS_CODEC_H264, - CEDRUS_CODEC_H265, - CEDRUS_CODEC_VP8, - CEDRUS_CODEC_LAST, -}; - enum cedrus_irq_status { CEDRUS_IRQ_NONE, CEDRUS_IRQ_ERROR, @@ -57,7 +49,7 @@ enum cedrus_h264_pic_type { struct cedrus_control { struct v4l2_ctrl_config cfg; - enum cedrus_codec codec; + unsigned int capabilities; }; struct cedrus_h264_run { @@ -108,7 +100,15 @@ struct cedrus_buffer { struct { unsigned int position; enum cedrus_h264_pic_type pic_type; + void *mv_col_buf; + dma_addr_t mv_col_buf_dma; + ssize_t mv_col_buf_size; } h264; + struct { + void *mv_col_buf; + dma_addr_t mv_col_buf_dma; + ssize_t mv_col_buf_size; + } h265; } codec; }; @@ -118,17 +118,14 @@ struct cedrus_ctx { struct v4l2_pix_format src_fmt; struct v4l2_pix_format dst_fmt; - enum cedrus_codec current_codec; + struct cedrus_dec_ops *current_codec; + unsigned int bit_depth; struct v4l2_ctrl_handler hdl; struct v4l2_ctrl **ctrls; union { struct { - void *mv_col_buf; - dma_addr_t mv_col_buf_dma; - ssize_t mv_col_buf_field_size; - ssize_t mv_col_buf_size; void *pic_info_buf; dma_addr_t pic_info_buf_dma; ssize_t pic_info_buf_size; @@ -142,10 +139,6 @@ struct cedrus_ctx { ssize_t intra_pred_buf_size; } h264; struct { - void *mv_col_buf; - dma_addr_t mv_col_buf_addr; - ssize_t mv_col_buf_size; - ssize_t mv_col_buf_unit_size; void *neighbor_info_buf; dma_addr_t neighbor_info_buf_addr; void *entry_points_buf; @@ -170,6 +163,8 @@ struct cedrus_dec_ops { int (*start)(struct cedrus_ctx *ctx); void (*stop)(struct cedrus_ctx *ctx); void (*trigger)(struct cedrus_ctx *ctx); + unsigned int (*extra_cap_size)(struct cedrus_ctx *ctx, + struct v4l2_pix_format *pix_fmt); }; struct cedrus_variant { @@ -185,7 +180,6 @@ struct cedrus_dev { struct platform_device *pdev; struct device *dev; struct v4l2_m2m_dev *m2m_dev; - struct cedrus_dec_ops *dec_ops[CEDRUS_CODEC_LAST]; /* Device file mutex */ struct mutex dev_mutex; @@ -268,6 +262,12 @@ vb2_to_cedrus_buffer(const struct vb2_buffer *p) return vb2_v4l2_to_cedrus_buffer(to_vb2_v4l2_buffer(p)); } +static inline bool +cedrus_is_capable(struct cedrus_ctx *ctx, unsigned int capabilities) +{ + return (ctx->dev->capabilities & capabilities) == capabilities; +} + void *cedrus_find_control_data(struct cedrus_ctx *ctx, u32 id); u32 cedrus_get_num_of_controls(struct cedrus_ctx *ctx, u32 id); diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_dec.c b/drivers/staging/media/sunxi/cedrus/cedrus_dec.c index e7f7602a5ab4..fbbf9e6f0f50 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_dec.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_dec.c @@ -94,7 +94,7 @@ void cedrus_device_run(void *priv) cedrus_dst_format_set(dev, &ctx->dst_fmt); - error = dev->dec_ops[ctx->current_codec]->setup(ctx, &run); + error = ctx->current_codec->setup(ctx, &run); if (error) v4l2_err(&ctx->dev->v4l2_dev, "Failed to setup decoding job: %d\n", error); @@ -110,7 +110,7 @@ void cedrus_device_run(void *priv) schedule_delayed_work(&dev->watchdog_work, msecs_to_jiffies(2000)); - dev->dec_ops[ctx->current_codec]->trigger(ctx); + ctx->current_codec->trigger(ctx); } else { v4l2_m2m_buf_done_and_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx, diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c index a8b236cd3800..dfb401df138a 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c @@ -54,17 +54,13 @@ static void cedrus_h264_write_sram(struct cedrus_dev *dev, cedrus_write(dev, VE_AVC_SRAM_PORT_DATA, *buffer++); } -static dma_addr_t cedrus_h264_mv_col_buf_addr(struct cedrus_ctx *ctx, - unsigned int position, +static dma_addr_t cedrus_h264_mv_col_buf_addr(struct cedrus_buffer *buf, unsigned int field) { - dma_addr_t addr = ctx->codec.h264.mv_col_buf_dma; - - /* Adjust for the position */ - addr += position * ctx->codec.h264.mv_col_buf_field_size * 2; + dma_addr_t addr = buf->codec.h264.mv_col_buf_dma; /* Adjust for the field */ - addr += field * ctx->codec.h264.mv_col_buf_field_size; + addr += field * buf->codec.h264.mv_col_buf_size / 2; return addr; } @@ -76,7 +72,6 @@ static void cedrus_fill_ref_pic(struct cedrus_ctx *ctx, struct cedrus_h264_sram_ref_pic *pic) { struct vb2_buffer *vbuf = &buf->m2m_buf.vb.vb2_buf; - unsigned int position = buf->codec.h264.position; pic->top_field_order_cnt = cpu_to_le32(top_field_order_cnt); pic->bottom_field_order_cnt = cpu_to_le32(bottom_field_order_cnt); @@ -84,14 +79,12 @@ static void cedrus_fill_ref_pic(struct cedrus_ctx *ctx, pic->luma_ptr = cpu_to_le32(cedrus_buf_addr(vbuf, &ctx->dst_fmt, 0)); pic->chroma_ptr = cpu_to_le32(cedrus_buf_addr(vbuf, &ctx->dst_fmt, 1)); - pic->mv_col_top_ptr = - cpu_to_le32(cedrus_h264_mv_col_buf_addr(ctx, position, 0)); - pic->mv_col_bot_ptr = - cpu_to_le32(cedrus_h264_mv_col_buf_addr(ctx, position, 1)); + pic->mv_col_top_ptr = cpu_to_le32(cedrus_h264_mv_col_buf_addr(buf, 0)); + pic->mv_col_bot_ptr = cpu_to_le32(cedrus_h264_mv_col_buf_addr(buf, 1)); } -static void cedrus_write_frame_list(struct cedrus_ctx *ctx, - struct cedrus_run *run) +static int cedrus_write_frame_list(struct cedrus_ctx *ctx, + struct cedrus_run *run) { struct cedrus_h264_sram_ref_pic pic_list[CEDRUS_H264_FRAME_NUM]; const struct v4l2_ctrl_h264_decode_params *decode = run->h264.decode_params; @@ -146,6 +139,31 @@ static void cedrus_write_frame_list(struct cedrus_ctx *ctx, output_buf = vb2_to_cedrus_buffer(&run->dst->vb2_buf); output_buf->codec.h264.position = position; + if (!output_buf->codec.h264.mv_col_buf_size) { + const struct v4l2_ctrl_h264_sps *sps = run->h264.sps; + unsigned int field_size; + + field_size = DIV_ROUND_UP(ctx->src_fmt.width, 16) * + DIV_ROUND_UP(ctx->src_fmt.height, 16) * 16; + if (!(sps->flags & V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE)) + field_size = field_size * 2; + if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY)) + field_size = field_size * 2; + + output_buf->codec.h264.mv_col_buf_size = field_size * 2; + /* Buffer is never accessed by CPU, so we can skip kernel mapping. */ + output_buf->codec.h264.mv_col_buf = + dma_alloc_attrs(dev->dev, + output_buf->codec.h264.mv_col_buf_size, + &output_buf->codec.h264.mv_col_buf_dma, + GFP_KERNEL, DMA_ATTR_NO_KERNEL_MAPPING); + + if (!output_buf->codec.h264.mv_col_buf) { + output_buf->codec.h264.mv_col_buf_size = 0; + return -ENOMEM; + } + } + if (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC) output_buf->codec.h264.pic_type = CEDRUS_H264_PIC_TYPE_FIELD; else if (sps->flags & V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD) @@ -162,6 +180,8 @@ static void cedrus_write_frame_list(struct cedrus_ctx *ctx, pic_list, sizeof(pic_list)); cedrus_write(dev, VE_H264_OUTPUT_FRAME_IDX, position); + + return 0; } #define CEDRUS_MAX_REF_IDX 32 @@ -496,8 +516,9 @@ static void cedrus_h264_irq_disable(struct cedrus_ctx *ctx) static int cedrus_h264_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) { struct cedrus_dev *dev = ctx->dev; + int ret; - cedrus_engine_enable(ctx, CEDRUS_CODEC_H264); + cedrus_engine_enable(ctx); cedrus_write(dev, VE_H264_SDROT_CTRL, 0); cedrus_write(dev, VE_H264_EXTRA_BUFFER1, @@ -506,7 +527,9 @@ static int cedrus_h264_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) ctx->codec.h264.neighbor_info_buf_dma); cedrus_write_scaling_lists(ctx, run); - cedrus_write_frame_list(ctx, run); + ret = cedrus_write_frame_list(ctx, run); + if (ret) + return ret; cedrus_set_params(ctx, run); @@ -517,8 +540,6 @@ static int cedrus_h264_start(struct cedrus_ctx *ctx) { struct cedrus_dev *dev = ctx->dev; unsigned int pic_info_size; - unsigned int field_size; - unsigned int mv_col_size; int ret; /* @@ -566,38 +587,6 @@ static int cedrus_h264_start(struct cedrus_ctx *ctx) goto err_pic_buf; } - field_size = DIV_ROUND_UP(ctx->src_fmt.width, 16) * - DIV_ROUND_UP(ctx->src_fmt.height, 16) * 16; - - /* - * FIXME: This is actually conditional to - * V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE not being set, we - * might have to rework this if memory efficiency ever is - * something we need to work on. - */ - field_size = field_size * 2; - - /* - * FIXME: This is actually conditional to - * V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY not being set, we might - * have to rework this if memory efficiency ever is something - * we need to work on. - */ - field_size = field_size * 2; - ctx->codec.h264.mv_col_buf_field_size = field_size; - - mv_col_size = field_size * 2 * CEDRUS_H264_FRAME_NUM; - ctx->codec.h264.mv_col_buf_size = mv_col_size; - ctx->codec.h264.mv_col_buf = - dma_alloc_attrs(dev->dev, - ctx->codec.h264.mv_col_buf_size, - &ctx->codec.h264.mv_col_buf_dma, - GFP_KERNEL, DMA_ATTR_NO_KERNEL_MAPPING); - if (!ctx->codec.h264.mv_col_buf) { - ret = -ENOMEM; - goto err_neighbor_buf; - } - if (ctx->src_fmt.width > 2048) { /* * Formulas for deblock and intra prediction buffer sizes @@ -613,7 +602,7 @@ static int cedrus_h264_start(struct cedrus_ctx *ctx) GFP_KERNEL, DMA_ATTR_NO_KERNEL_MAPPING); if (!ctx->codec.h264.deblk_buf) { ret = -ENOMEM; - goto err_mv_col_buf; + goto err_neighbor_buf; } /* @@ -641,12 +630,6 @@ err_deblk_buf: ctx->codec.h264.deblk_buf_dma, DMA_ATTR_NO_KERNEL_MAPPING); -err_mv_col_buf: - dma_free_attrs(dev->dev, ctx->codec.h264.mv_col_buf_size, - ctx->codec.h264.mv_col_buf, - ctx->codec.h264.mv_col_buf_dma, - DMA_ATTR_NO_KERNEL_MAPPING); - err_neighbor_buf: dma_free_attrs(dev->dev, CEDRUS_NEIGHBOR_INFO_BUF_SIZE, ctx->codec.h264.neighbor_info_buf, @@ -664,11 +647,26 @@ err_pic_buf: static void cedrus_h264_stop(struct cedrus_ctx *ctx) { struct cedrus_dev *dev = ctx->dev; + struct cedrus_buffer *buf; + struct vb2_queue *vq; + unsigned int i; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + for (i = 0; i < vq->num_buffers; i++) { + buf = vb2_to_cedrus_buffer(vb2_get_buffer(vq, i)); + + if (buf->codec.h264.mv_col_buf_size > 0) { + dma_free_attrs(dev->dev, + buf->codec.h264.mv_col_buf_size, + buf->codec.h264.mv_col_buf, + buf->codec.h264.mv_col_buf_dma, + DMA_ATTR_NO_KERNEL_MAPPING); + + buf->codec.h264.mv_col_buf_size = 0; + } + } - dma_free_attrs(dev->dev, ctx->codec.h264.mv_col_buf_size, - ctx->codec.h264.mv_col_buf, - ctx->codec.h264.mv_col_buf_dma, - DMA_ATTR_NO_KERNEL_MAPPING); dma_free_attrs(dev->dev, CEDRUS_NEIGHBOR_INFO_BUF_SIZE, ctx->codec.h264.neighbor_info_buf, ctx->codec.h264.neighbor_info_buf_dma, diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c index 4952fc17f3e6..fc9297232456 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c @@ -41,6 +41,19 @@ struct cedrus_h265_sram_pred_weight { __s8 offset; } __packed; +static unsigned int cedrus_h265_2bit_size(unsigned int width, + unsigned int height) +{ + /* + * Vendor library additionally aligns width and height to 16, + * but all capture formats are already aligned to that anyway, + * so we can skip that here. All formats are also one form of + * YUV 4:2:0 or another, so we can safely assume multiplication + * factor of 1.5. + */ + return ALIGN(width / 4, 32) * height * 3 / 2; +} + static enum cedrus_irq_status cedrus_h265_irq_status(struct cedrus_ctx *ctx) { struct cedrus_dev *dev = ctx->dev; @@ -90,12 +103,13 @@ static void cedrus_h265_sram_write_data(struct cedrus_dev *dev, void *data, } static inline dma_addr_t -cedrus_h265_frame_info_mv_col_buf_addr(struct cedrus_ctx *ctx, - unsigned int index, unsigned int field) +cedrus_h265_frame_info_mv_col_buf_addr(struct vb2_buffer *buf, + unsigned int field) { - return ctx->codec.h265.mv_col_buf_addr + index * - ctx->codec.h265.mv_col_buf_unit_size + - field * ctx->codec.h265.mv_col_buf_unit_size / 2; + struct cedrus_buffer *cedrus_buf = vb2_to_cedrus_buffer(buf); + + return cedrus_buf->codec.h265.mv_col_buf_dma + + field * cedrus_buf->codec.h265.mv_col_buf_size / 2; } static void cedrus_h265_frame_info_write_single(struct cedrus_ctx *ctx, @@ -108,9 +122,8 @@ static void cedrus_h265_frame_info_write_single(struct cedrus_ctx *ctx, dma_addr_t dst_luma_addr = cedrus_dst_buf_addr(ctx, buf, 0); dma_addr_t dst_chroma_addr = cedrus_dst_buf_addr(ctx, buf, 1); dma_addr_t mv_col_buf_addr[2] = { - cedrus_h265_frame_info_mv_col_buf_addr(ctx, buf->index, 0), - cedrus_h265_frame_info_mv_col_buf_addr(ctx, buf->index, - field_pic ? 1 : 0) + cedrus_h265_frame_info_mv_col_buf_addr(buf, 0), + cedrus_h265_frame_info_mv_col_buf_addr(buf, field_pic ? 1 : 0) }; u32 offset = VE_DEC_H265_SRAM_OFFSET_FRAME_INFO + VE_DEC_H265_SRAM_OFFSET_FRAME_INFO_UNIT * index; @@ -242,6 +255,18 @@ static void cedrus_h265_skip_bits(struct cedrus_dev *dev, int num) } } +static u32 cedrus_h265_show_bits(struct cedrus_dev *dev, int num) +{ + cedrus_write(dev, VE_DEC_H265_TRIGGER, + VE_DEC_H265_TRIGGER_SHOW_BITS | + VE_DEC_H265_TRIGGER_TYPE_N_BITS(num)); + + cedrus_wait_for(dev, VE_DEC_H265_STATUS, + VE_DEC_H265_STATUS_VLD_BUSY); + + return cedrus_read(dev, VE_DEC_H265_BITS_READ); +} + static void cedrus_h265_write_scaling_list(struct cedrus_ctx *ctx, struct cedrus_run *run) { @@ -400,13 +425,14 @@ static int cedrus_h265_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) unsigned int width_in_ctb_luma, ctb_size_luma; unsigned int log2_max_luma_coding_block_size; unsigned int ctb_addr_x, ctb_addr_y; + struct cedrus_buffer *cedrus_buf; dma_addr_t src_buf_addr; dma_addr_t src_buf_end_addr; u32 chroma_log2_weight_denom; u32 num_entry_point_offsets; u32 output_pic_list_index; u32 pic_order_cnt[2]; - u8 *padding; + u8 padding; int count; u32 reg; @@ -416,6 +442,7 @@ static int cedrus_h265_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) decode_params = run->h265.decode_params; pred_weight_table = &slice_params->pred_weight_table; num_entry_point_offsets = slice_params->num_entry_point_offsets; + cedrus_buf = vb2_to_cedrus_buffer(&run->dst->vb2_buf); /* * If entry points offsets are present, we should get them @@ -433,37 +460,31 @@ static int cedrus_h265_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) DIV_ROUND_UP(sps->pic_width_in_luma_samples, ctb_size_luma); /* MV column buffer size and allocation. */ - if (!ctx->codec.h265.mv_col_buf_size) { - unsigned int num_buffers = - run->dst->vb2_buf.vb2_queue->num_buffers; - + if (!cedrus_buf->codec.h265.mv_col_buf_size) { /* * Each CTB requires a MV col buffer with a specific unit size. * Since the address is given with missing lsb bits, 1 KiB is * added to each buffer to ensure proper alignment. */ - ctx->codec.h265.mv_col_buf_unit_size = + cedrus_buf->codec.h265.mv_col_buf_size = DIV_ROUND_UP(ctx->src_fmt.width, ctb_size_luma) * DIV_ROUND_UP(ctx->src_fmt.height, ctb_size_luma) * CEDRUS_H265_MV_COL_BUF_UNIT_CTB_SIZE + SZ_1K; - ctx->codec.h265.mv_col_buf_size = num_buffers * - ctx->codec.h265.mv_col_buf_unit_size; - /* Buffer is never accessed by CPU, so we can skip kernel mapping. */ - ctx->codec.h265.mv_col_buf = + cedrus_buf->codec.h265.mv_col_buf = dma_alloc_attrs(dev->dev, - ctx->codec.h265.mv_col_buf_size, - &ctx->codec.h265.mv_col_buf_addr, + cedrus_buf->codec.h265.mv_col_buf_size, + &cedrus_buf->codec.h265.mv_col_buf_dma, GFP_KERNEL, DMA_ATTR_NO_KERNEL_MAPPING); - if (!ctx->codec.h265.mv_col_buf) { - ctx->codec.h265.mv_col_buf_size = 0; + if (!cedrus_buf->codec.h265.mv_col_buf) { + cedrus_buf->codec.h265.mv_col_buf_size = 0; return -ENOMEM; } } /* Activate H265 engine. */ - cedrus_engine_enable(ctx, CEDRUS_CODEC_H265); + cedrus_engine_enable(ctx); /* Source offset and length in bits. */ @@ -520,21 +541,22 @@ static int cedrus_h265_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) if (slice_params->data_byte_offset == 0) return -EOPNOTSUPP; - padding = (u8 *)vb2_plane_vaddr(&run->src->vb2_buf, 0) + - slice_params->data_byte_offset - 1; + cedrus_h265_skip_bits(dev, (slice_params->data_byte_offset - 1) * 8); + + padding = cedrus_h265_show_bits(dev, 8); /* at least one bit must be set in that byte */ - if (*padding == 0) + if (padding == 0) return -EINVAL; for (count = 0; count < 8; count++) - if (*padding & (1 << count)) + if (padding & (1 << count)) break; /* Include the one bit. */ count++; - cedrus_h265_skip_bits(dev, slice_params->data_byte_offset * 8 - count); + cedrus_h265_skip_bits(dev, 8 - count); /* Bitstream parameters. */ @@ -793,6 +815,18 @@ static int cedrus_h265_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) VE_DEC_H265_SRAM_OFFSET_PRED_WEIGHT_CHROMA_L1); } + if (ctx->bit_depth > 8) { + unsigned int stride = ALIGN(ctx->dst_fmt.width / 4, 32); + + reg = ctx->dst_fmt.sizeimage - + cedrus_h265_2bit_size(ctx->dst_fmt.width, + ctx->dst_fmt.height); + cedrus_write(dev, VE_DEC_H265_OFFSET_ADDR_FIRST_OUT, reg); + + reg = VE_DEC_H265_10BIT_CONFIGURE_FIRST_2BIT_STRIDE(stride); + cedrus_write(dev, VE_DEC_H265_10BIT_CONFIGURE, reg); + } + /* Enable appropriate interruptions. */ cedrus_write(dev, VE_DEC_H265_CTRL, VE_DEC_H265_CTRL_IRQ_MASK); @@ -803,9 +837,6 @@ static int cedrus_h265_start(struct cedrus_ctx *ctx) { struct cedrus_dev *dev = ctx->dev; - /* The buffer size is calculated at setup time. */ - ctx->codec.h265.mv_col_buf_size = 0; - /* Buffer is never accessed by CPU, so we can skip kernel mapping. */ ctx->codec.h265.neighbor_info_buf = dma_alloc_attrs(dev->dev, CEDRUS_H265_NEIGHBOR_INFO_BUF_SIZE, @@ -832,14 +863,24 @@ static int cedrus_h265_start(struct cedrus_ctx *ctx) static void cedrus_h265_stop(struct cedrus_ctx *ctx) { struct cedrus_dev *dev = ctx->dev; + struct cedrus_buffer *buf; + struct vb2_queue *vq; + unsigned int i; - if (ctx->codec.h265.mv_col_buf_size > 0) { - dma_free_attrs(dev->dev, ctx->codec.h265.mv_col_buf_size, - ctx->codec.h265.mv_col_buf, - ctx->codec.h265.mv_col_buf_addr, - DMA_ATTR_NO_KERNEL_MAPPING); + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + for (i = 0; i < vq->num_buffers; i++) { + buf = vb2_to_cedrus_buffer(vb2_get_buffer(vq, i)); - ctx->codec.h265.mv_col_buf_size = 0; + if (buf->codec.h265.mv_col_buf_size > 0) { + dma_free_attrs(dev->dev, + buf->codec.h265.mv_col_buf_size, + buf->codec.h265.mv_col_buf, + buf->codec.h265.mv_col_buf_dma, + DMA_ATTR_NO_KERNEL_MAPPING); + + buf->codec.h265.mv_col_buf_size = 0; + } } dma_free_attrs(dev->dev, CEDRUS_H265_NEIGHBOR_INFO_BUF_SIZE, @@ -858,6 +899,15 @@ static void cedrus_h265_trigger(struct cedrus_ctx *ctx) cedrus_write(dev, VE_DEC_H265_TRIGGER, VE_DEC_H265_TRIGGER_DEC_SLICE); } +static unsigned int cedrus_h265_extra_cap_size(struct cedrus_ctx *ctx, + struct v4l2_pix_format *pix_fmt) +{ + if (ctx->bit_depth > 8) + return cedrus_h265_2bit_size(pix_fmt->width, pix_fmt->height); + + return 0; +} + struct cedrus_dec_ops cedrus_dec_ops_h265 = { .irq_clear = cedrus_h265_irq_clear, .irq_disable = cedrus_h265_irq_disable, @@ -866,4 +916,5 @@ struct cedrus_dec_ops cedrus_dec_ops_h265 = { .start = cedrus_h265_start, .stop = cedrus_h265_stop, .trigger = cedrus_h265_trigger, + .extra_cap_size = cedrus_h265_extra_cap_size, }; diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c index a6470a89851e..fa86a658fdc6 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c @@ -31,7 +31,7 @@ #include "cedrus_hw.h" #include "cedrus_regs.h" -int cedrus_engine_enable(struct cedrus_ctx *ctx, enum cedrus_codec codec) +int cedrus_engine_enable(struct cedrus_ctx *ctx) { u32 reg = 0; @@ -42,18 +42,18 @@ int cedrus_engine_enable(struct cedrus_ctx *ctx, enum cedrus_codec codec) reg |= VE_MODE_REC_WR_MODE_2MB; reg |= VE_MODE_DDR_MODE_BW_128; - switch (codec) { - case CEDRUS_CODEC_MPEG2: + switch (ctx->src_fmt.pixelformat) { + case V4L2_PIX_FMT_MPEG2_SLICE: reg |= VE_MODE_DEC_MPEG; break; /* H.264 and VP8 both use the same decoding mode bit. */ - case CEDRUS_CODEC_H264: - case CEDRUS_CODEC_VP8: + case V4L2_PIX_FMT_H264_SLICE: + case V4L2_PIX_FMT_VP8_FRAME: reg |= VE_MODE_DEC_H264; break; - case CEDRUS_CODEC_H265: + case V4L2_PIX_FMT_HEVC_SLICE: reg |= VE_MODE_DEC_H265; break; @@ -132,12 +132,12 @@ static irqreturn_t cedrus_irq(int irq, void *data) return IRQ_NONE; } - status = dev->dec_ops[ctx->current_codec]->irq_status(ctx); + status = ctx->current_codec->irq_status(ctx); if (status == CEDRUS_IRQ_NONE) return IRQ_NONE; - dev->dec_ops[ctx->current_codec]->irq_disable(ctx); - dev->dec_ops[ctx->current_codec]->irq_clear(ctx); + ctx->current_codec->irq_disable(ctx); + ctx->current_codec->irq_clear(ctx); if (status == CEDRUS_IRQ_ERROR) state = VB2_BUF_STATE_ERROR; diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.h b/drivers/staging/media/sunxi/cedrus/cedrus_hw.h index 7c92f00e36da..6f1e701b1ea8 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.h +++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.h @@ -16,7 +16,7 @@ #ifndef _CEDRUS_HW_H_ #define _CEDRUS_HW_H_ -int cedrus_engine_enable(struct cedrus_ctx *ctx, enum cedrus_codec codec); +int cedrus_engine_enable(struct cedrus_ctx *ctx); void cedrus_engine_disable(struct cedrus_dev *dev); void cedrus_dst_format_set(struct cedrus_dev *dev, diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c index c1128d2cd555..10e98f08aafc 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c @@ -66,7 +66,7 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) quantisation = run->mpeg2.quantisation; /* Activate MPEG engine. */ - cedrus_engine_enable(ctx, CEDRUS_CODEC_MPEG2); + cedrus_engine_enable(ctx); /* Set intra quantisation matrix. */ matrix = quantisation->intra_quantiser_matrix; diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h index d81f7513ade0..05e6cbc548ab 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h +++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h @@ -498,6 +498,22 @@ #define VE_DEC_H265_LOW_ADDR (VE_ENGINE_DEC_H265 + 0x80) +#define VE_DEC_H265_OFFSET_ADDR_FIRST_OUT (VE_ENGINE_DEC_H265 + 0x84) +#define VE_DEC_H265_OFFSET_ADDR_SECOND_OUT (VE_ENGINE_DEC_H265 + 0x88) + +#define VE_DEC_H265_SECOND_OUT_FMT_8BIT_PLUS_2BIT 0 +#define VE_DEC_H265_SECOND_OUT_FMT_P010 1 +#define VE_DEC_H265_SECOND_OUT_FMT_10BIT_4x4_TILED 2 + +#define VE_DEC_H265_10BIT_CONFIGURE_SECOND_OUT_FMT(v) \ + SHIFT_AND_MASK_BITS(v, 24, 23) +#define VE_DEC_H265_10BIT_CONFIGURE_SECOND_2BIT_ENABLE BIT(22) +#define VE_DEC_H265_10BIT_CONFIGURE_SECOND_2BIT_STRIDE(v) \ + SHIFT_AND_MASK_BITS(v, 21, 11) +#define VE_DEC_H265_10BIT_CONFIGURE_FIRST_2BIT_STRIDE(v) \ + SHIFT_AND_MASK_BITS(v, 10, 0) +#define VE_DEC_H265_10BIT_CONFIGURE (VE_ENGINE_DEC_H265 + 0x8c) + #define VE_DEC_H265_LOW_ADDR_PRIMARY_CHROMA(a) \ SHIFT_AND_MASK_BITS(a, 31, 24) #define VE_DEC_H265_LOW_ADDR_SECONDARY_CHROMA(a) \ @@ -505,6 +521,8 @@ #define VE_DEC_H265_LOW_ADDR_ENTRY_POINTS_BUF(a) \ SHIFT_AND_MASK_BITS(a, 7, 0) +#define VE_DEC_H265_BITS_READ (VE_ENGINE_DEC_H265 + 0xdc) + #define VE_DEC_H265_SRAM_OFFSET (VE_ENGINE_DEC_H265 + 0xe0) #define VE_DEC_H265_SRAM_OFFSET_PRED_WEIGHT_LUMA_L0 0x00 diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c index 66714609b577..b00feaf4072c 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c @@ -56,13 +56,13 @@ static struct cedrus_format cedrus_formats[] = { .capabilities = CEDRUS_CAPABILITY_VP8_DEC, }, { - .pixelformat = V4L2_PIX_FMT_NV12_32L32, + .pixelformat = V4L2_PIX_FMT_NV12, .directions = CEDRUS_DECODE_DST, + .capabilities = CEDRUS_CAPABILITY_UNTILED, }, { - .pixelformat = V4L2_PIX_FMT_NV12, + .pixelformat = V4L2_PIX_FMT_NV12_32L32, .directions = CEDRUS_DECODE_DST, - .capabilities = CEDRUS_CAPABILITY_UNTILED, }, }; @@ -73,8 +73,8 @@ static inline struct cedrus_ctx *cedrus_file2ctx(struct file *file) return container_of(file->private_data, struct cedrus_ctx, fh); } -static struct cedrus_format *cedrus_find_format(u32 pixelformat, u32 directions, - unsigned int capabilities) +static struct cedrus_format *cedrus_find_format(struct cedrus_ctx *ctx, + u32 pixelformat, u32 directions) { struct cedrus_format *first_valid_fmt = NULL; struct cedrus_format *fmt; @@ -83,7 +83,7 @@ static struct cedrus_format *cedrus_find_format(u32 pixelformat, u32 directions, for (i = 0; i < CEDRUS_FORMATS_COUNT; i++) { fmt = &cedrus_formats[i]; - if ((fmt->capabilities & capabilities) != fmt->capabilities || + if (!cedrus_is_capable(ctx, fmt->capabilities) || !(fmt->directions & directions)) continue; @@ -177,19 +177,13 @@ static int cedrus_enum_fmt(struct file *file, struct v4l2_fmtdesc *f, u32 direction) { struct cedrus_ctx *ctx = cedrus_file2ctx(file); - struct cedrus_dev *dev = ctx->dev; - unsigned int capabilities = dev->capabilities; - struct cedrus_format *fmt; unsigned int i, index; /* Index among formats that match the requested direction. */ index = 0; for (i = 0; i < CEDRUS_FORMATS_COUNT; i++) { - fmt = &cedrus_formats[i]; - - if (fmt->capabilities && (fmt->capabilities & capabilities) != - fmt->capabilities) + if (!cedrus_is_capable(ctx, cedrus_formats[i].capabilities)) continue; if (!(cedrus_formats[i].directions & direction)) @@ -241,15 +235,12 @@ static int cedrus_g_fmt_vid_out(struct file *file, void *priv, return 0; } -static int cedrus_try_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) +static int cedrus_try_fmt_vid_cap_p(struct cedrus_ctx *ctx, + struct v4l2_pix_format *pix_fmt) { - struct cedrus_ctx *ctx = cedrus_file2ctx(file); - struct cedrus_dev *dev = ctx->dev; - struct v4l2_pix_format *pix_fmt = &f->fmt.pix; struct cedrus_format *fmt = - cedrus_find_format(pix_fmt->pixelformat, CEDRUS_DECODE_DST, - dev->capabilities); + cedrus_find_format(ctx, pix_fmt->pixelformat, + CEDRUS_DECODE_DST); if (!fmt) return -EINVAL; @@ -259,18 +250,25 @@ static int cedrus_try_fmt_vid_cap(struct file *file, void *priv, pix_fmt->height = ctx->src_fmt.height; cedrus_prepare_format(pix_fmt); + if (ctx->current_codec->extra_cap_size) + pix_fmt->sizeimage += + ctx->current_codec->extra_cap_size(ctx, pix_fmt); + return 0; } -static int cedrus_try_fmt_vid_out(struct file *file, void *priv, +static int cedrus_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct cedrus_ctx *ctx = cedrus_file2ctx(file); - struct cedrus_dev *dev = ctx->dev; - struct v4l2_pix_format *pix_fmt = &f->fmt.pix; + return cedrus_try_fmt_vid_cap_p(cedrus_file2ctx(file), &f->fmt.pix); +} + +static int cedrus_try_fmt_vid_out_p(struct cedrus_ctx *ctx, + struct v4l2_pix_format *pix_fmt) +{ struct cedrus_format *fmt = - cedrus_find_format(pix_fmt->pixelformat, CEDRUS_DECODE_SRC, - dev->capabilities); + cedrus_find_format(ctx, pix_fmt->pixelformat, + CEDRUS_DECODE_SRC); if (!fmt) return -EINVAL; @@ -281,6 +279,12 @@ static int cedrus_try_fmt_vid_out(struct file *file, void *priv, return 0; } +static int cedrus_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return cedrus_try_fmt_vid_out_p(cedrus_file2ctx(file), &f->fmt.pix); +} + static int cedrus_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { @@ -301,18 +305,76 @@ static int cedrus_s_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int cedrus_s_fmt_vid_out(struct file *file, void *priv, - struct v4l2_format *f) +void cedrus_reset_cap_format(struct cedrus_ctx *ctx) +{ + ctx->dst_fmt.pixelformat = 0; + cedrus_try_fmt_vid_cap_p(ctx, &ctx->dst_fmt); +} + +static int cedrus_s_fmt_vid_out_p(struct cedrus_ctx *ctx, + struct v4l2_pix_format *pix_fmt) { - struct cedrus_ctx *ctx = cedrus_file2ctx(file); struct vb2_queue *vq; - struct vb2_queue *peer_vq; int ret; - ret = cedrus_try_fmt_vid_out(file, priv, f); + ret = cedrus_try_fmt_vid_out_p(ctx, pix_fmt); if (ret) return ret; + ctx->src_fmt = *pix_fmt; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + switch (ctx->src_fmt.pixelformat) { + case V4L2_PIX_FMT_H264_SLICE: + case V4L2_PIX_FMT_HEVC_SLICE: + vq->subsystem_flags |= + VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; + break; + default: + vq->subsystem_flags &= + ~VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; + break; + } + + switch (ctx->src_fmt.pixelformat) { + case V4L2_PIX_FMT_MPEG2_SLICE: + ctx->current_codec = &cedrus_dec_ops_mpeg2; + break; + case V4L2_PIX_FMT_H264_SLICE: + ctx->current_codec = &cedrus_dec_ops_h264; + break; + case V4L2_PIX_FMT_HEVC_SLICE: + ctx->current_codec = &cedrus_dec_ops_h265; + break; + case V4L2_PIX_FMT_VP8_FRAME: + ctx->current_codec = &cedrus_dec_ops_vp8; + break; + } + + /* Propagate format information to capture. */ + ctx->dst_fmt.colorspace = pix_fmt->colorspace; + ctx->dst_fmt.xfer_func = pix_fmt->xfer_func; + ctx->dst_fmt.ycbcr_enc = pix_fmt->ycbcr_enc; + ctx->dst_fmt.quantization = pix_fmt->quantization; + cedrus_reset_cap_format(ctx); + + return 0; +} + +void cedrus_reset_out_format(struct cedrus_ctx *ctx) +{ + ctx->src_fmt.pixelformat = 0; + cedrus_s_fmt_vid_out_p(ctx, &ctx->src_fmt); +} + +static int cedrus_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cedrus_ctx *ctx = cedrus_file2ctx(file); + struct vb2_queue *vq; + struct vb2_queue *peer_vq; + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); /* * In order to support dynamic resolution change, @@ -332,34 +394,7 @@ static int cedrus_s_fmt_vid_out(struct file *file, void *priv, if (vb2_is_busy(peer_vq)) return -EBUSY; - ret = cedrus_try_fmt_vid_out(file, priv, f); - if (ret) - return ret; - - ctx->src_fmt = f->fmt.pix; - - switch (ctx->src_fmt.pixelformat) { - case V4L2_PIX_FMT_H264_SLICE: - case V4L2_PIX_FMT_HEVC_SLICE: - vq->subsystem_flags |= - VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; - break; - default: - vq->subsystem_flags &= - ~VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; - break; - } - - /* Propagate format information to capture. */ - ctx->dst_fmt.colorspace = f->fmt.pix.colorspace; - ctx->dst_fmt.xfer_func = f->fmt.pix.xfer_func; - ctx->dst_fmt.ycbcr_enc = f->fmt.pix.ycbcr_enc; - ctx->dst_fmt.quantization = f->fmt.pix.quantization; - ctx->dst_fmt.width = ctx->src_fmt.width; - ctx->dst_fmt.height = ctx->src_fmt.height; - cedrus_prepare_format(&ctx->dst_fmt); - - return 0; + return cedrus_s_fmt_vid_out_p(cedrus_file2ctx(file), &f->fmt.pix); } const struct v4l2_ioctl_ops cedrus_ioctl_ops = { @@ -475,34 +510,13 @@ static int cedrus_start_streaming(struct vb2_queue *vq, unsigned int count) struct cedrus_dev *dev = ctx->dev; int ret = 0; - switch (ctx->src_fmt.pixelformat) { - case V4L2_PIX_FMT_MPEG2_SLICE: - ctx->current_codec = CEDRUS_CODEC_MPEG2; - break; - - case V4L2_PIX_FMT_H264_SLICE: - ctx->current_codec = CEDRUS_CODEC_H264; - break; - - case V4L2_PIX_FMT_HEVC_SLICE: - ctx->current_codec = CEDRUS_CODEC_H265; - break; - - case V4L2_PIX_FMT_VP8_FRAME: - ctx->current_codec = CEDRUS_CODEC_VP8; - break; - - default: - return -EINVAL; - } - if (V4L2_TYPE_IS_OUTPUT(vq->type)) { ret = pm_runtime_resume_and_get(dev->dev); if (ret < 0) goto err_cleanup; - if (dev->dec_ops[ctx->current_codec]->start) { - ret = dev->dec_ops[ctx->current_codec]->start(ctx); + if (ctx->current_codec->start) { + ret = ctx->current_codec->start(ctx); if (ret) goto err_pm; } @@ -524,8 +538,8 @@ static void cedrus_stop_streaming(struct vb2_queue *vq) struct cedrus_dev *dev = ctx->dev; if (V4L2_TYPE_IS_OUTPUT(vq->type)) { - if (dev->dec_ops[ctx->current_codec]->stop) - dev->dec_ops[ctx->current_codec]->stop(ctx); + if (ctx->current_codec->stop) + ctx->current_codec->stop(ctx); pm_runtime_put(dev->dev); } @@ -548,7 +562,7 @@ static void cedrus_buf_request_complete(struct vb2_buffer *vb) v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); } -static struct vb2_ops cedrus_qops = { +static const struct vb2_ops cedrus_qops = { .queue_setup = cedrus_queue_setup, .buf_prepare = cedrus_buf_prepare, .buf_queue = cedrus_buf_queue, diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.h b/drivers/staging/media/sunxi/cedrus/cedrus_video.h index 05050c0a0921..8e1afc16a6a1 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_video.h +++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.h @@ -27,5 +27,7 @@ extern const struct v4l2_ioctl_ops cedrus_ioctl_ops; int cedrus_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq); void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt); +void cedrus_reset_cap_format(struct cedrus_ctx *ctx); +void cedrus_reset_out_format(struct cedrus_ctx *ctx); #endif diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c index f7714baae37d..969677a3bbf9 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c @@ -662,7 +662,7 @@ static int cedrus_vp8_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) int header_size; u32 reg; - cedrus_engine_enable(ctx, CEDRUS_CODEC_VP8); + cedrus_engine_enable(ctx); cedrus_write(dev, VE_H264_CTRL, VE_H264_CTRL_VP8); diff --git a/drivers/staging/media/sunxi/sun6i-isp/Kconfig b/drivers/staging/media/sunxi/sun6i-isp/Kconfig new file mode 100644 index 000000000000..68dcae9cd7d7 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SUN6I_ISP + tristate "Allwinner A31 Image Signal Processor (ISP) Driver" + depends on V4L_PLATFORM_DRIVERS && VIDEO_DEV + depends on ARCH_SUNXI || COMPILE_TEST + depends on PM && COMMON_CLK && HAS_DMA + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + select V4L2_FWNODE + select REGMAP_MMIO + help + Support for the Allwinner A31 Image Signal Processor (ISP), also + found on other platforms such as the A80, A83T or V3/V3s. diff --git a/drivers/staging/media/sunxi/sun6i-isp/Makefile b/drivers/staging/media/sunxi/sun6i-isp/Makefile new file mode 100644 index 000000000000..da1034785144 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +sun6i-isp-y += sun6i_isp.o sun6i_isp_proc.o sun6i_isp_capture.o sun6i_isp_params.o + +obj-$(CONFIG_VIDEO_SUN6I_ISP) += sun6i-isp.o diff --git a/drivers/staging/media/sunxi/sun6i-isp/TODO.txt b/drivers/staging/media/sunxi/sun6i-isp/TODO.txt new file mode 100644 index 000000000000..1e3236edc1ab --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/TODO.txt @@ -0,0 +1,6 @@ +Unstaging requirements: +- Add uAPI support and documentation for the configuration of all the hardware + modules and description of the statistics data structures; +- Add support for statistics reporting; +- Add userspace support in libcamera which demonstrates the ability to receive + statistics and adapt hardware modules configuration accordingly; diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.c new file mode 100644 index 000000000000..7b7947509b69 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mc.h> + +#include "sun6i_isp.h" +#include "sun6i_isp_capture.h" +#include "sun6i_isp_params.h" +#include "sun6i_isp_proc.h" +#include "sun6i_isp_reg.h" + +/* Helpers */ + +u32 sun6i_isp_load_read(struct sun6i_isp_device *isp_dev, u32 offset) +{ + u32 *data = (u32 *)(isp_dev->tables.load.data + offset); + + return *data; +} + +void sun6i_isp_load_write(struct sun6i_isp_device *isp_dev, u32 offset, + u32 value) +{ + u32 *data = (u32 *)(isp_dev->tables.load.data + offset); + + *data = value; +} + +/* State */ + +/* + * The ISP works with a load buffer, which gets copied to the actual registers + * by the hardware before processing a frame when a specific flag is set. + * This is represented by tracking the ISP state in the different parts of + * the code with explicit sync points: + * - state update: to update the load buffer for the next frame if necessary; + * - state complete: to indicate that the state update was applied. + */ + +static void sun6i_isp_state_ready(struct sun6i_isp_device *isp_dev) +{ + struct regmap *regmap = isp_dev->regmap; + u32 value; + + regmap_read(regmap, SUN6I_ISP_FE_CTRL_REG, &value); + value |= SUN6I_ISP_FE_CTRL_PARA_READY; + regmap_write(regmap, SUN6I_ISP_FE_CTRL_REG, value); +} + +static void sun6i_isp_state_complete(struct sun6i_isp_device *isp_dev) +{ + unsigned long flags; + + spin_lock_irqsave(&isp_dev->state_lock, flags); + + sun6i_isp_capture_state_complete(isp_dev); + sun6i_isp_params_state_complete(isp_dev); + + spin_unlock_irqrestore(&isp_dev->state_lock, flags); +} + +void sun6i_isp_state_update(struct sun6i_isp_device *isp_dev, bool ready_hold) +{ + bool update = false; + unsigned long flags; + + spin_lock_irqsave(&isp_dev->state_lock, flags); + + sun6i_isp_capture_state_update(isp_dev, &update); + sun6i_isp_params_state_update(isp_dev, &update); + + if (update && !ready_hold) + sun6i_isp_state_ready(isp_dev); + + spin_unlock_irqrestore(&isp_dev->state_lock, flags); +} + +/* Tables */ + +static int sun6i_isp_table_setup(struct sun6i_isp_device *isp_dev, + struct sun6i_isp_table *table) +{ + table->data = dma_alloc_coherent(isp_dev->dev, table->size, + &table->address, GFP_KERNEL); + if (!table->data) + return -ENOMEM; + + return 0; +} + +static void sun6i_isp_table_cleanup(struct sun6i_isp_device *isp_dev, + struct sun6i_isp_table *table) +{ + dma_free_coherent(isp_dev->dev, table->size, table->data, + table->address); +} + +void sun6i_isp_tables_configure(struct sun6i_isp_device *isp_dev) +{ + struct regmap *regmap = isp_dev->regmap; + + regmap_write(regmap, SUN6I_ISP_REG_LOAD_ADDR_REG, + SUN6I_ISP_ADDR_VALUE(isp_dev->tables.load.address)); + + regmap_write(regmap, SUN6I_ISP_REG_SAVE_ADDR_REG, + SUN6I_ISP_ADDR_VALUE(isp_dev->tables.save.address)); + + regmap_write(regmap, SUN6I_ISP_LUT_TABLE_ADDR_REG, + SUN6I_ISP_ADDR_VALUE(isp_dev->tables.lut.address)); + + regmap_write(regmap, SUN6I_ISP_DRC_TABLE_ADDR_REG, + SUN6I_ISP_ADDR_VALUE(isp_dev->tables.drc.address)); + + regmap_write(regmap, SUN6I_ISP_STATS_ADDR_REG, + SUN6I_ISP_ADDR_VALUE(isp_dev->tables.stats.address)); +} + +static int sun6i_isp_tables_setup(struct sun6i_isp_device *isp_dev, + const struct sun6i_isp_variant *variant) +{ + struct sun6i_isp_tables *tables = &isp_dev->tables; + int ret; + + tables->load.size = variant->table_load_save_size; + ret = sun6i_isp_table_setup(isp_dev, &tables->load); + if (ret) + return ret; + + tables->save.size = variant->table_load_save_size; + ret = sun6i_isp_table_setup(isp_dev, &tables->save); + if (ret) + return ret; + + tables->lut.size = variant->table_lut_size; + ret = sun6i_isp_table_setup(isp_dev, &tables->lut); + if (ret) + return ret; + + tables->drc.size = variant->table_drc_size; + ret = sun6i_isp_table_setup(isp_dev, &tables->drc); + if (ret) + return ret; + + tables->stats.size = variant->table_stats_size; + ret = sun6i_isp_table_setup(isp_dev, &tables->stats); + if (ret) + return ret; + + return 0; +} + +static void sun6i_isp_tables_cleanup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_tables *tables = &isp_dev->tables; + + sun6i_isp_table_cleanup(isp_dev, &tables->stats); + sun6i_isp_table_cleanup(isp_dev, &tables->drc); + sun6i_isp_table_cleanup(isp_dev, &tables->lut); + sun6i_isp_table_cleanup(isp_dev, &tables->save); + sun6i_isp_table_cleanup(isp_dev, &tables->load); +} + +/* Media */ + +static const struct media_device_ops sun6i_isp_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +/* V4L2 */ + +static int sun6i_isp_v4l2_setup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_v4l2 *v4l2 = &isp_dev->v4l2; + struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; + struct media_device *media_dev = &v4l2->media_dev; + struct device *dev = isp_dev->dev; + int ret; + + /* Media Device */ + + strscpy(media_dev->model, SUN6I_ISP_DESCRIPTION, + sizeof(media_dev->model)); + media_dev->ops = &sun6i_isp_media_ops; + media_dev->hw_revision = 0; + media_dev->dev = dev; + + media_device_init(media_dev); + + ret = media_device_register(media_dev); + if (ret) { + dev_err(dev, "failed to register media device\n"); + return ret; + } + + /* V4L2 Device */ + + v4l2_dev->mdev = media_dev; + + ret = v4l2_device_register(dev, v4l2_dev); + if (ret) { + dev_err(dev, "failed to register v4l2 device\n"); + goto error_media; + } + + return 0; + +error_media: + media_device_unregister(media_dev); + media_device_cleanup(media_dev); + + return ret; +} + +static void sun6i_isp_v4l2_cleanup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_v4l2 *v4l2 = &isp_dev->v4l2; + + media_device_unregister(&v4l2->media_dev); + v4l2_device_unregister(&v4l2->v4l2_dev); + media_device_cleanup(&v4l2->media_dev); +} + +/* Platform */ + +static irqreturn_t sun6i_isp_interrupt(int irq, void *private) +{ + struct sun6i_isp_device *isp_dev = private; + struct regmap *regmap = isp_dev->regmap; + u32 status = 0, enable = 0; + + regmap_read(regmap, SUN6I_ISP_FE_INT_STA_REG, &status); + regmap_read(regmap, SUN6I_ISP_FE_INT_EN_REG, &enable); + + if (!status) + return IRQ_NONE; + else if (!(status & enable)) + goto complete; + + /* + * The ISP working cycle starts with a params-load, which makes the + * state from the load buffer active. Then it starts processing the + * frame and gives a finish interrupt. Soon after that, the next state + * coming from the load buffer will be applied for the next frame, + * giving a params-load as well. + * + * Because both frame finish and params-load are received almost + * at the same time (one ISR call), handle them in chronology order. + */ + + if (status & SUN6I_ISP_FE_INT_STA_FINISH) + sun6i_isp_capture_finish(isp_dev); + + if (status & SUN6I_ISP_FE_INT_STA_PARA_LOAD) { + sun6i_isp_state_complete(isp_dev); + sun6i_isp_state_update(isp_dev, false); + } + +complete: + regmap_write(regmap, SUN6I_ISP_FE_INT_STA_REG, status); + + return IRQ_HANDLED; +} + +static int sun6i_isp_suspend(struct device *dev) +{ + struct sun6i_isp_device *isp_dev = dev_get_drvdata(dev); + + reset_control_assert(isp_dev->reset); + clk_disable_unprepare(isp_dev->clock_ram); + clk_disable_unprepare(isp_dev->clock_mod); + + return 0; +} + +static int sun6i_isp_resume(struct device *dev) +{ + struct sun6i_isp_device *isp_dev = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(isp_dev->reset); + if (ret) { + dev_err(dev, "failed to deassert reset\n"); + return ret; + } + + ret = clk_prepare_enable(isp_dev->clock_mod); + if (ret) { + dev_err(dev, "failed to enable module clock\n"); + goto error_reset; + } + + ret = clk_prepare_enable(isp_dev->clock_ram); + if (ret) { + dev_err(dev, "failed to enable ram clock\n"); + goto error_clock_mod; + } + + return 0; + +error_clock_mod: + clk_disable_unprepare(isp_dev->clock_mod); + +error_reset: + reset_control_assert(isp_dev->reset); + + return ret; +} + +static const struct dev_pm_ops sun6i_isp_pm_ops = { + .runtime_suspend = sun6i_isp_suspend, + .runtime_resume = sun6i_isp_resume, +}; + +static const struct regmap_config sun6i_isp_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x400, +}; + +static int sun6i_isp_resources_setup(struct sun6i_isp_device *isp_dev, + struct platform_device *platform_dev) +{ + struct device *dev = isp_dev->dev; + void __iomem *io_base; + int irq; + int ret; + + /* Registers */ + + io_base = devm_platform_ioremap_resource(platform_dev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + isp_dev->regmap = devm_regmap_init_mmio_clk(dev, "bus", io_base, + &sun6i_isp_regmap_config); + if (IS_ERR(isp_dev->regmap)) { + dev_err(dev, "failed to init register map\n"); + return PTR_ERR(isp_dev->regmap); + } + + /* Clocks */ + + isp_dev->clock_mod = devm_clk_get(dev, "mod"); + if (IS_ERR(isp_dev->clock_mod)) { + dev_err(dev, "failed to acquire module clock\n"); + return PTR_ERR(isp_dev->clock_mod); + } + + isp_dev->clock_ram = devm_clk_get(dev, "ram"); + if (IS_ERR(isp_dev->clock_ram)) { + dev_err(dev, "failed to acquire ram clock\n"); + return PTR_ERR(isp_dev->clock_ram); + } + + ret = clk_set_rate_exclusive(isp_dev->clock_mod, 297000000); + if (ret) { + dev_err(dev, "failed to set mod clock rate\n"); + return ret; + } + + /* Reset */ + + isp_dev->reset = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(isp_dev->reset)) { + dev_err(dev, "failed to acquire reset\n"); + ret = PTR_ERR(isp_dev->reset); + goto error_clock_rate_exclusive; + } + + /* Interrupt */ + + irq = platform_get_irq(platform_dev, 0); + if (irq < 0) { + dev_err(dev, "failed to get interrupt\n"); + ret = -ENXIO; + goto error_clock_rate_exclusive; + } + + ret = devm_request_irq(dev, irq, sun6i_isp_interrupt, IRQF_SHARED, + SUN6I_ISP_NAME, isp_dev); + if (ret) { + dev_err(dev, "failed to request interrupt\n"); + goto error_clock_rate_exclusive; + } + + /* Runtime PM */ + + pm_runtime_enable(dev); + + return 0; + +error_clock_rate_exclusive: + clk_rate_exclusive_put(isp_dev->clock_mod); + + return ret; +} + +static void sun6i_isp_resources_cleanup(struct sun6i_isp_device *isp_dev) +{ + struct device *dev = isp_dev->dev; + + pm_runtime_disable(dev); + clk_rate_exclusive_put(isp_dev->clock_mod); +} + +static int sun6i_isp_probe(struct platform_device *platform_dev) +{ + struct sun6i_isp_device *isp_dev; + struct device *dev = &platform_dev->dev; + const struct sun6i_isp_variant *variant; + int ret; + + variant = of_device_get_match_data(dev); + if (!variant) + return -EINVAL; + + isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL); + if (!isp_dev) + return -ENOMEM; + + isp_dev->dev = dev; + platform_set_drvdata(platform_dev, isp_dev); + + spin_lock_init(&isp_dev->state_lock); + + ret = sun6i_isp_resources_setup(isp_dev, platform_dev); + if (ret) + return ret; + + ret = sun6i_isp_tables_setup(isp_dev, variant); + if (ret) { + dev_err(dev, "failed to setup tables\n"); + goto error_resources; + } + + ret = sun6i_isp_v4l2_setup(isp_dev); + if (ret) { + dev_err(dev, "failed to setup v4l2\n"); + goto error_tables; + } + + ret = sun6i_isp_proc_setup(isp_dev); + if (ret) { + dev_err(dev, "failed to setup proc\n"); + goto error_v4l2; + } + + ret = sun6i_isp_capture_setup(isp_dev); + if (ret) { + dev_err(dev, "failed to setup capture\n"); + goto error_proc; + } + + ret = sun6i_isp_params_setup(isp_dev); + if (ret) { + dev_err(dev, "failed to setup params\n"); + goto error_capture; + } + + return 0; + +error_capture: + sun6i_isp_capture_cleanup(isp_dev); + +error_proc: + sun6i_isp_proc_cleanup(isp_dev); + +error_v4l2: + sun6i_isp_v4l2_cleanup(isp_dev); + +error_tables: + sun6i_isp_tables_cleanup(isp_dev); + +error_resources: + sun6i_isp_resources_cleanup(isp_dev); + + return ret; +} + +static int sun6i_isp_remove(struct platform_device *platform_dev) +{ + struct sun6i_isp_device *isp_dev = platform_get_drvdata(platform_dev); + + sun6i_isp_params_cleanup(isp_dev); + sun6i_isp_capture_cleanup(isp_dev); + sun6i_isp_proc_cleanup(isp_dev); + sun6i_isp_v4l2_cleanup(isp_dev); + sun6i_isp_tables_cleanup(isp_dev); + sun6i_isp_resources_cleanup(isp_dev); + + return 0; +} + +/* + * History of sun6i-isp: + * - sun4i-a10-isp: initial ISP tied to the CSI0 controller, + * apparently unused in software implementations; + * - sun6i-a31-isp: separate ISP loosely based on sun4i-a10-isp, + * adding extra modules and features; + * - sun9i-a80-isp: based on sun6i-a31-isp with some register offset changes + * and new modules like saturation and cnr; + * - sun8i-a23-isp/sun8i-h3-isp: based on sun9i-a80-isp with most modules + * related to raw removed; + * - sun8i-a83t-isp: based on sun9i-a80-isp with some register offset changes + * - sun8i-v3s-isp: based on sun8i-a83t-isp with a new disc module; + */ + +static const struct sun6i_isp_variant sun8i_v3s_isp_variant = { + .table_load_save_size = 0x1000, + .table_lut_size = 0xe00, + .table_drc_size = 0x600, + .table_stats_size = 0x2100, +}; + +static const struct of_device_id sun6i_isp_of_match[] = { + { + .compatible = "allwinner,sun8i-v3s-isp", + .data = &sun8i_v3s_isp_variant, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, sun6i_isp_of_match); + +static struct platform_driver sun6i_isp_platform_driver = { + .probe = sun6i_isp_probe, + .remove = sun6i_isp_remove, + .driver = { + .name = SUN6I_ISP_NAME, + .of_match_table = of_match_ptr(sun6i_isp_of_match), + .pm = &sun6i_isp_pm_ops, + }, +}; + +module_platform_driver(sun6i_isp_platform_driver); + +MODULE_DESCRIPTION("Allwinner A31 Image Signal Processor driver"); +MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.h new file mode 100644 index 000000000000..0e5f188319ff --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_ISP_H_ +#define _SUN6I_ISP_H_ + +#include <media/v4l2-device.h> +#include <media/videobuf2-v4l2.h> + +#include "sun6i_isp_capture.h" +#include "sun6i_isp_params.h" +#include "sun6i_isp_proc.h" + +#define SUN6I_ISP_NAME "sun6i-isp" +#define SUN6I_ISP_DESCRIPTION "Allwinner A31 ISP Device" + +enum sun6i_isp_port { + SUN6I_ISP_PORT_CSI0 = 0, + SUN6I_ISP_PORT_CSI1 = 1, +}; + +struct sun6i_isp_buffer { + struct vb2_v4l2_buffer v4l2_buffer; + struct list_head list; +}; + +struct sun6i_isp_v4l2 { + struct v4l2_device v4l2_dev; + struct media_device media_dev; +}; + +struct sun6i_isp_table { + void *data; + dma_addr_t address; + unsigned int size; +}; + +struct sun6i_isp_tables { + struct sun6i_isp_table load; + struct sun6i_isp_table save; + + struct sun6i_isp_table lut; + struct sun6i_isp_table drc; + struct sun6i_isp_table stats; +}; + +struct sun6i_isp_device { + struct device *dev; + + struct sun6i_isp_tables tables; + + struct sun6i_isp_v4l2 v4l2; + struct sun6i_isp_proc proc; + struct sun6i_isp_capture capture; + struct sun6i_isp_params params; + + struct regmap *regmap; + struct clk *clock_mod; + struct clk *clock_ram; + struct reset_control *reset; + + spinlock_t state_lock; /* State helpers lock. */ +}; + +struct sun6i_isp_variant { + unsigned int table_load_save_size; + unsigned int table_lut_size; + unsigned int table_drc_size; + unsigned int table_stats_size; +}; + +/* Helpers */ + +u32 sun6i_isp_load_read(struct sun6i_isp_device *isp_dev, u32 offset); +void sun6i_isp_load_write(struct sun6i_isp_device *isp_dev, u32 offset, + u32 value); +u32 sun6i_isp_address_value(dma_addr_t address); + +/* State */ + +void sun6i_isp_state_update(struct sun6i_isp_device *isp_dev, bool ready_hold); + +/* Tables */ + +void sun6i_isp_tables_configure(struct sun6i_isp_device *isp_dev); + +#endif diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.c new file mode 100644 index 000000000000..4b592820845a --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-v4l2.h> + +#include "sun6i_isp.h" +#include "sun6i_isp_capture.h" +#include "sun6i_isp_proc.h" +#include "sun6i_isp_reg.h" + +/* Helpers */ + +void sun6i_isp_capture_dimensions(struct sun6i_isp_device *isp_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = isp_dev->capture.format.fmt.pix.width; + if (height) + *height = isp_dev->capture.format.fmt.pix.height; +} + +void sun6i_isp_capture_format(struct sun6i_isp_device *isp_dev, + u32 *pixelformat) +{ + if (pixelformat) + *pixelformat = isp_dev->capture.format.fmt.pix.pixelformat; +} + +/* Format */ + +static const struct sun6i_isp_capture_format sun6i_isp_capture_formats[] = { + { + .pixelformat = V4L2_PIX_FMT_NV12, + .output_format = SUN6I_ISP_OUTPUT_FMT_YUV420SP, + }, + { + .pixelformat = V4L2_PIX_FMT_NV21, + .output_format = SUN6I_ISP_OUTPUT_FMT_YVU420SP, + }, +}; + +const struct sun6i_isp_capture_format * +sun6i_isp_capture_format_find(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_isp_capture_formats); i++) + if (sun6i_isp_capture_formats[i].pixelformat == pixelformat) + return &sun6i_isp_capture_formats[i]; + + return NULL; +} + +/* Capture */ + +static void +sun6i_isp_capture_buffer_configure(struct sun6i_isp_device *isp_dev, + struct sun6i_isp_buffer *isp_buffer) +{ + const struct v4l2_format_info *info; + struct vb2_buffer *vb2_buffer; + unsigned int width, height; + unsigned int width_aligned; + dma_addr_t address; + u32 pixelformat; + + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + address = vb2_dma_contig_plane_dma_addr(vb2_buffer, 0); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_Y_ADDR0_REG, + SUN6I_ISP_ADDR_VALUE(address)); + + sun6i_isp_capture_dimensions(isp_dev, &width, &height); + sun6i_isp_capture_format(isp_dev, &pixelformat); + + info = v4l2_format_info(pixelformat); + if (WARN_ON(!info)) + return; + + /* Stride needs to be aligned to 4. */ + width_aligned = ALIGN(width, 2); + + if (info->comp_planes > 1) { + address += info->bpp[0] * width_aligned * height; + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_U_ADDR0_REG, + SUN6I_ISP_ADDR_VALUE(address)); + } + + if (info->comp_planes > 2) { + address += info->bpp[1] * + DIV_ROUND_UP(width_aligned, info->hdiv) * + DIV_ROUND_UP(height, info->vdiv); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_V_ADDR0_REG, + SUN6I_ISP_ADDR_VALUE(address)); + } +} + +void sun6i_isp_capture_configure(struct sun6i_isp_device *isp_dev) +{ + unsigned int width, height; + unsigned int stride_luma, stride_chroma = 0; + unsigned int stride_luma_div4, stride_chroma_div4; + const struct sun6i_isp_capture_format *format; + const struct v4l2_format_info *info; + u32 pixelformat; + + sun6i_isp_capture_dimensions(isp_dev, &width, &height); + sun6i_isp_capture_format(isp_dev, &pixelformat); + + format = sun6i_isp_capture_format_find(pixelformat); + if (WARN_ON(!format)) + return; + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_SIZE_CFG_REG, + SUN6I_ISP_MCH_SIZE_CFG_WIDTH(width) | + SUN6I_ISP_MCH_SIZE_CFG_HEIGHT(height)); + + info = v4l2_format_info(pixelformat); + if (WARN_ON(!info)) + return; + + stride_luma = width * info->bpp[0]; + stride_luma_div4 = DIV_ROUND_UP(stride_luma, 4); + + if (info->comp_planes > 1) { + stride_chroma = width * info->bpp[1] / info->hdiv; + stride_chroma_div4 = DIV_ROUND_UP(stride_chroma, 4); + } + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_CFG_REG, + SUN6I_ISP_MCH_CFG_EN | + SUN6I_ISP_MCH_CFG_OUTPUT_FMT(format->output_format) | + SUN6I_ISP_MCH_CFG_STRIDE_Y_DIV4(stride_luma_div4) | + SUN6I_ISP_MCH_CFG_STRIDE_UV_DIV4(stride_chroma_div4)); +} + +/* State */ + +static void sun6i_isp_capture_state_cleanup(struct sun6i_isp_device *isp_dev, + bool error) +{ + struct sun6i_isp_capture_state *state = &isp_dev->capture.state; + struct sun6i_isp_buffer **isp_buffer_states[] = { + &state->pending, &state->current, &state->complete, + }; + struct sun6i_isp_buffer *isp_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&state->lock, flags); + + for (i = 0; i < ARRAY_SIZE(isp_buffer_states); i++) { + isp_buffer = *isp_buffer_states[i]; + if (!isp_buffer) + continue; + + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + + *isp_buffer_states[i] = NULL; + } + + list_for_each_entry(isp_buffer, &state->queue, list) { + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + INIT_LIST_HEAD(&state->queue); + + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_isp_capture_state_update(struct sun6i_isp_device *isp_dev, + bool *update) +{ + struct sun6i_isp_capture_state *state = &isp_dev->capture.state; + struct sun6i_isp_buffer *isp_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (list_empty(&state->queue)) + goto complete; + + if (state->pending) + goto complete; + + isp_buffer = list_first_entry(&state->queue, struct sun6i_isp_buffer, + list); + + sun6i_isp_capture_buffer_configure(isp_dev, isp_buffer); + + list_del(&isp_buffer->list); + + state->pending = isp_buffer; + + if (update) + *update = true; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_isp_capture_state_complete(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_capture_state *state = &isp_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (!state->pending) + goto complete; + + state->complete = state->current; + state->current = state->pending; + state->pending = NULL; + + if (state->complete) { + struct sun6i_isp_buffer *isp_buffer = state->complete; + struct vb2_buffer *vb2_buffer = + &isp_buffer->v4l2_buffer.vb2_buf; + + vb2_buffer->timestamp = ktime_get_ns(); + isp_buffer->v4l2_buffer.sequence = state->sequence; + + vb2_buffer_done(vb2_buffer, VB2_BUF_STATE_DONE); + + state->complete = NULL; + } + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_isp_capture_finish(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_capture_state *state = &isp_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + state->sequence++; + spin_unlock_irqrestore(&state->lock, flags); +} + +/* Queue */ + +static int sun6i_isp_capture_queue_setup(struct vb2_queue *queue, + unsigned int *buffers_count, + unsigned int *planes_count, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + unsigned int size = isp_dev->capture.format.fmt.pix.sizeimage; + + if (*planes_count) + return sizes[0] < size ? -EINVAL : 0; + + *planes_count = 1; + sizes[0] = size; + + return 0; +} + +static int sun6i_isp_capture_buffer_prepare(struct vb2_buffer *vb2_buffer) +{ + struct sun6i_isp_device *isp_dev = + vb2_get_drv_priv(vb2_buffer->vb2_queue); + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + unsigned int size = isp_dev->capture.format.fmt.pix.sizeimage; + + if (vb2_plane_size(vb2_buffer, 0) < size) { + v4l2_err(v4l2_dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb2_buffer, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb2_buffer, 0, size); + + return 0; +} + +static void sun6i_isp_capture_buffer_queue(struct vb2_buffer *vb2_buffer) +{ + struct sun6i_isp_device *isp_dev = + vb2_get_drv_priv(vb2_buffer->vb2_queue); + struct sun6i_isp_capture_state *state = &isp_dev->capture.state; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(vb2_buffer); + struct sun6i_isp_buffer *isp_buffer = + container_of(v4l2_buffer, struct sun6i_isp_buffer, v4l2_buffer); + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + list_add_tail(&isp_buffer->list, &state->queue); + spin_unlock_irqrestore(&state->lock, flags); + + /* Update the state to schedule our buffer as soon as possible. */ + if (state->streaming) + sun6i_isp_state_update(isp_dev, false); +} + +static int sun6i_isp_capture_start_streaming(struct vb2_queue *queue, + unsigned int count) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + struct sun6i_isp_capture_state *state = &isp_dev->capture.state; + struct video_device *video_dev = &isp_dev->capture.video_dev; + struct v4l2_subdev *subdev = &isp_dev->proc.subdev; + int ret; + + state->sequence = 0; + + ret = video_device_pipeline_alloc_start(video_dev); + if (ret < 0) + goto error_state; + + state->streaming = true; + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto error_streaming; + + return 0; + +error_streaming: + state->streaming = false; + + video_device_pipeline_stop(video_dev); + +error_state: + sun6i_isp_capture_state_cleanup(isp_dev, false); + + return ret; +} + +static void sun6i_isp_capture_stop_streaming(struct vb2_queue *queue) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + struct sun6i_isp_capture_state *state = &isp_dev->capture.state; + struct video_device *video_dev = &isp_dev->capture.video_dev; + struct v4l2_subdev *subdev = &isp_dev->proc.subdev; + + v4l2_subdev_call(subdev, video, s_stream, 0); + + state->streaming = false; + + video_device_pipeline_stop(video_dev); + + sun6i_isp_capture_state_cleanup(isp_dev, true); +} + +static const struct vb2_ops sun6i_isp_capture_queue_ops = { + .queue_setup = sun6i_isp_capture_queue_setup, + .buf_prepare = sun6i_isp_capture_buffer_prepare, + .buf_queue = sun6i_isp_capture_buffer_queue, + .start_streaming = sun6i_isp_capture_start_streaming, + .stop_streaming = sun6i_isp_capture_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* Video Device */ + +static void sun6i_isp_capture_format_prepare(struct v4l2_format *format) +{ + struct v4l2_pix_format *pix_format = &format->fmt.pix; + const struct v4l2_format_info *info; + unsigned int width, height; + unsigned int width_aligned; + unsigned int i; + + v4l_bound_align_image(&pix_format->width, SUN6I_ISP_CAPTURE_WIDTH_MIN, + SUN6I_ISP_CAPTURE_WIDTH_MAX, 1, + &pix_format->height, SUN6I_ISP_CAPTURE_HEIGHT_MIN, + SUN6I_ISP_CAPTURE_HEIGHT_MAX, 1, 0); + + if (!sun6i_isp_capture_format_find(pix_format->pixelformat)) + pix_format->pixelformat = + sun6i_isp_capture_formats[0].pixelformat; + + info = v4l2_format_info(pix_format->pixelformat); + if (WARN_ON(!info)) + return; + + width = pix_format->width; + height = pix_format->height; + + /* Stride needs to be aligned to 4. */ + width_aligned = ALIGN(width, 2); + + pix_format->bytesperline = width_aligned * info->bpp[0]; + pix_format->sizeimage = 0; + + for (i = 0; i < info->comp_planes; i++) { + unsigned int hdiv = (i == 0) ? 1 : info->hdiv; + unsigned int vdiv = (i == 0) ? 1 : info->vdiv; + + pix_format->sizeimage += info->bpp[i] * + DIV_ROUND_UP(width_aligned, hdiv) * + DIV_ROUND_UP(height, vdiv); + } + + pix_format->field = V4L2_FIELD_NONE; + + pix_format->colorspace = V4L2_COLORSPACE_RAW; + pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; + pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_isp_capture_querycap(struct file *file, void *private, + struct v4l2_capability *capability) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + struct video_device *video_dev = &isp_dev->capture.video_dev; + + strscpy(capability->driver, SUN6I_ISP_NAME, sizeof(capability->driver)); + strscpy(capability->card, video_dev->name, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", dev_name(isp_dev->dev)); + + return 0; +} + +static int sun6i_isp_capture_enum_fmt(struct file *file, void *private, + struct v4l2_fmtdesc *fmtdesc) +{ + u32 index = fmtdesc->index; + + if (index >= ARRAY_SIZE(sun6i_isp_capture_formats)) + return -EINVAL; + + fmtdesc->pixelformat = sun6i_isp_capture_formats[index].pixelformat; + + return 0; +} + +static int sun6i_isp_capture_g_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + + *format = isp_dev->capture.format; + + return 0; +} + +static int sun6i_isp_capture_s_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + + if (vb2_is_busy(&isp_dev->capture.queue)) + return -EBUSY; + + sun6i_isp_capture_format_prepare(format); + + isp_dev->capture.format = *format; + + return 0; +} + +static int sun6i_isp_capture_try_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + sun6i_isp_capture_format_prepare(format); + + return 0; +} + +static int sun6i_isp_capture_enum_input(struct file *file, void *private, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, "Camera", sizeof(input->name)); + + return 0; +} + +static int sun6i_isp_capture_g_input(struct file *file, void *private, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int sun6i_isp_capture_s_input(struct file *file, void *private, + unsigned int index) +{ + if (index != 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_ioctl_ops sun6i_isp_capture_ioctl_ops = { + .vidioc_querycap = sun6i_isp_capture_querycap, + + .vidioc_enum_fmt_vid_cap = sun6i_isp_capture_enum_fmt, + .vidioc_g_fmt_vid_cap = sun6i_isp_capture_g_fmt, + .vidioc_s_fmt_vid_cap = sun6i_isp_capture_s_fmt, + .vidioc_try_fmt_vid_cap = sun6i_isp_capture_try_fmt, + + .vidioc_enum_input = sun6i_isp_capture_enum_input, + .vidioc_g_input = sun6i_isp_capture_g_input, + .vidioc_s_input = sun6i_isp_capture_s_input, + + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static int sun6i_isp_capture_open(struct file *file) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + struct video_device *video_dev = &isp_dev->capture.video_dev; + struct mutex *lock = &isp_dev->capture.lock; + int ret; + + if (mutex_lock_interruptible(lock)) + return -ERESTARTSYS; + + ret = v4l2_pipeline_pm_get(&video_dev->entity); + if (ret) + goto error_mutex; + + ret = v4l2_fh_open(file); + if (ret) + goto error_pipeline; + + mutex_unlock(lock); + + return 0; + +error_pipeline: + v4l2_pipeline_pm_put(&video_dev->entity); + +error_mutex: + mutex_unlock(lock); + + return ret; +} + +static int sun6i_isp_capture_release(struct file *file) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + struct video_device *video_dev = &isp_dev->capture.video_dev; + struct mutex *lock = &isp_dev->capture.lock; + + mutex_lock(lock); + + _vb2_fop_release(file, NULL); + v4l2_pipeline_pm_put(&video_dev->entity); + + mutex_unlock(lock); + + return 0; +} + +static const struct v4l2_file_operations sun6i_isp_capture_fops = { + .owner = THIS_MODULE, + .open = sun6i_isp_capture_open, + .release = sun6i_isp_capture_release, + .unlocked_ioctl = video_ioctl2, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +/* Media Entity */ + +static int sun6i_isp_capture_link_validate(struct media_link *link) +{ + struct video_device *video_dev = + media_entity_to_video_device(link->sink->entity); + struct sun6i_isp_device *isp_dev = video_get_drvdata(video_dev); + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + unsigned int capture_width, capture_height; + unsigned int proc_width, proc_height; + + sun6i_isp_capture_dimensions(isp_dev, &capture_width, &capture_height); + sun6i_isp_proc_dimensions(isp_dev, &proc_width, &proc_height); + + /* No cropping/scaling is supported (yet). */ + if (capture_width != proc_width || capture_height != proc_height) { + v4l2_err(v4l2_dev, + "invalid input/output dimensions: %ux%u/%ux%u\n", + proc_width, proc_height, capture_width, + capture_height); + return -EINVAL; + } + + return 0; +} + +static const struct media_entity_operations sun6i_isp_capture_entity_ops = { + .link_validate = sun6i_isp_capture_link_validate, +}; + +/* Capture */ + +int sun6i_isp_capture_setup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_capture *capture = &isp_dev->capture; + struct sun6i_isp_capture_state *state = &capture->state; + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + struct v4l2_subdev *proc_subdev = &isp_dev->proc.subdev; + struct video_device *video_dev = &capture->video_dev; + struct vb2_queue *queue = &capture->queue; + struct media_pad *pad = &capture->pad; + struct v4l2_format *format = &capture->format; + struct v4l2_pix_format *pix_format = &format->fmt.pix; + int ret; + + /* State */ + + INIT_LIST_HEAD(&state->queue); + spin_lock_init(&state->lock); + + /* Media Entity */ + + video_dev->entity.ops = &sun6i_isp_capture_entity_ops; + + /* Media Pads */ + + pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&video_dev->entity, 1, pad); + if (ret) + goto error_mutex; + + /* Queue */ + + mutex_init(&capture->lock); + + queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + queue->io_modes = VB2_MMAP | VB2_DMABUF; + queue->buf_struct_size = sizeof(struct sun6i_isp_buffer); + queue->ops = &sun6i_isp_capture_queue_ops; + queue->mem_ops = &vb2_dma_contig_memops; + queue->min_buffers_needed = 2; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->lock = &capture->lock; + queue->dev = isp_dev->dev; + queue->drv_priv = isp_dev; + + ret = vb2_queue_init(queue); + if (ret) { + v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Format */ + + format->type = queue->type; + pix_format->pixelformat = sun6i_isp_capture_formats[0].pixelformat; + pix_format->width = 1280; + pix_format->height = 720; + + sun6i_isp_capture_format_prepare(format); + + /* Video Device */ + + strscpy(video_dev->name, SUN6I_ISP_CAPTURE_NAME, + sizeof(video_dev->name)); + video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + video_dev->vfl_dir = VFL_DIR_RX; + video_dev->release = video_device_release_empty; + video_dev->fops = &sun6i_isp_capture_fops; + video_dev->ioctl_ops = &sun6i_isp_capture_ioctl_ops; + video_dev->v4l2_dev = v4l2_dev; + video_dev->queue = queue; + video_dev->lock = &capture->lock; + + video_set_drvdata(video_dev, isp_dev); + + ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(v4l2_dev, "failed to register video device: %d\n", + ret); + goto error_media_entity; + } + + /* Media Pad Link */ + + ret = media_create_pad_link(&proc_subdev->entity, + SUN6I_ISP_PROC_PAD_SOURCE, + &video_dev->entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n", + proc_subdev->entity.name, SUN6I_ISP_PROC_PAD_SOURCE, + video_dev->entity.name, 0); + goto error_video_device; + } + + return 0; + +error_video_device: + vb2_video_unregister_device(video_dev); + +error_media_entity: + media_entity_cleanup(&video_dev->entity); + +error_mutex: + mutex_destroy(&capture->lock); + + return ret; +} + +void sun6i_isp_capture_cleanup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_capture *capture = &isp_dev->capture; + struct video_device *video_dev = &capture->video_dev; + + vb2_video_unregister_device(video_dev); + media_entity_cleanup(&video_dev->entity); + mutex_destroy(&capture->lock); +} diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.h new file mode 100644 index 000000000000..0e3e4fa7a0f4 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_ISP_CAPTURE_H_ +#define _SUN6I_ISP_CAPTURE_H_ + +#include <media/v4l2-device.h> + +#define SUN6I_ISP_CAPTURE_NAME "sun6i-isp-capture" + +#define SUN6I_ISP_CAPTURE_WIDTH_MIN 16 +#define SUN6I_ISP_CAPTURE_WIDTH_MAX 3264 +#define SUN6I_ISP_CAPTURE_HEIGHT_MIN 16 +#define SUN6I_ISP_CAPTURE_HEIGHT_MAX 2448 + +struct sun6i_isp_device; + +struct sun6i_isp_capture_format { + u32 pixelformat; + u8 output_format; +}; + +#undef current +struct sun6i_isp_capture_state { + struct list_head queue; + spinlock_t lock; /* Queue and buffers lock. */ + + struct sun6i_isp_buffer *pending; + struct sun6i_isp_buffer *current; + struct sun6i_isp_buffer *complete; + + unsigned int sequence; + bool streaming; +}; + +struct sun6i_isp_capture { + struct sun6i_isp_capture_state state; + + struct video_device video_dev; + struct vb2_queue queue; + struct mutex lock; /* Queue lock. */ + struct media_pad pad; + + struct v4l2_format format; +}; + +/* Helpers */ + +void sun6i_isp_capture_dimensions(struct sun6i_isp_device *isp_dev, + unsigned int *width, unsigned int *height); +void sun6i_isp_capture_format(struct sun6i_isp_device *isp_dev, + u32 *pixelformat); + +/* Format */ + +const struct sun6i_isp_capture_format * +sun6i_isp_capture_format_find(u32 pixelformat); + +/* Capture */ + +void sun6i_isp_capture_configure(struct sun6i_isp_device *isp_dev); + +/* State */ + +void sun6i_isp_capture_state_update(struct sun6i_isp_device *isp_dev, + bool *update); +void sun6i_isp_capture_state_complete(struct sun6i_isp_device *isp_dev); +void sun6i_isp_capture_finish(struct sun6i_isp_device *isp_dev); + +/* Capture */ + +int sun6i_isp_capture_setup(struct sun6i_isp_device *isp_dev); +void sun6i_isp_capture_cleanup(struct sun6i_isp_device *isp_dev); + +#endif diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c new file mode 100644 index 000000000000..8039e311cb1c --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/videobuf2-vmalloc.h> +#include <media/videobuf2-v4l2.h> + +#include "sun6i_isp.h" +#include "sun6i_isp_params.h" +#include "sun6i_isp_reg.h" +#include "uapi/sun6i-isp-config.h" + +/* Params */ + +static const struct sun6i_isp_params_config sun6i_isp_params_config_default = { + .modules_used = SUN6I_ISP_MODULE_BAYER, + + .bayer = { + .offset_r = 32, + .offset_gr = 32, + .offset_gb = 32, + .offset_b = 32, + + .gain_r = 256, + .gain_gr = 256, + .gain_gb = 256, + .gain_b = 256, + + }, + + .bdnf = { + .in_dis_min = 8, + .in_dis_max = 16, + + .coefficients_g = { 15, 4, 1 }, + .coefficients_rb = { 15, 4 }, + }, +}; + +static void sun6i_isp_params_configure_ob(struct sun6i_isp_device *isp_dev) +{ + unsigned int width, height; + + sun6i_isp_proc_dimensions(isp_dev, &width, &height); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_SIZE_REG, + SUN6I_ISP_OB_SIZE_WIDTH(width) | + SUN6I_ISP_OB_SIZE_HEIGHT(height)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_VALID_REG, + SUN6I_ISP_OB_VALID_WIDTH(width) | + SUN6I_ISP_OB_VALID_HEIGHT(height)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_SRC0_VALID_START_REG, + SUN6I_ISP_OB_SRC0_VALID_START_HORZ(0) | + SUN6I_ISP_OB_SRC0_VALID_START_VERT(0)); +} + +static void sun6i_isp_params_configure_ae(struct sun6i_isp_device *isp_dev) +{ + /* These are default values that need to be set to get an output. */ + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_AE_CFG_REG, + SUN6I_ISP_AE_CFG_LOW_BRI_TH(0xff) | + SUN6I_ISP_AE_CFG_HORZ_NUM(8) | + SUN6I_ISP_AE_CFG_HIGH_BRI_TH(0xf00) | + SUN6I_ISP_AE_CFG_VERT_NUM(8)); +} + +static void +sun6i_isp_params_configure_bayer(struct sun6i_isp_device *isp_dev, + const struct sun6i_isp_params_config *config) +{ + const struct sun6i_isp_params_config_bayer *bayer = &config->bayer; + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_OFFSET0_REG, + SUN6I_ISP_BAYER_OFFSET0_R(bayer->offset_r) | + SUN6I_ISP_BAYER_OFFSET0_GR(bayer->offset_gr)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_OFFSET1_REG, + SUN6I_ISP_BAYER_OFFSET1_GB(bayer->offset_gb) | + SUN6I_ISP_BAYER_OFFSET1_B(bayer->offset_b)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_GAIN0_REG, + SUN6I_ISP_BAYER_GAIN0_R(bayer->gain_r) | + SUN6I_ISP_BAYER_GAIN0_GR(bayer->gain_gr)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_GAIN1_REG, + SUN6I_ISP_BAYER_GAIN1_GB(bayer->gain_gb) | + SUN6I_ISP_BAYER_GAIN1_B(bayer->gain_b)); +} + +static void sun6i_isp_params_configure_wb(struct sun6i_isp_device *isp_dev) +{ + /* These are default values that need to be set to get an output. */ + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_GAIN0_REG, + SUN6I_ISP_WB_GAIN0_R(256) | + SUN6I_ISP_WB_GAIN0_GR(256)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_GAIN1_REG, + SUN6I_ISP_WB_GAIN1_GB(256) | + SUN6I_ISP_WB_GAIN1_B(256)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_CFG_REG, + SUN6I_ISP_WB_CFG_CLIP(0xfff)); +} + +static void sun6i_isp_params_configure_base(struct sun6i_isp_device *isp_dev) +{ + sun6i_isp_params_configure_ae(isp_dev); + sun6i_isp_params_configure_ob(isp_dev); + sun6i_isp_params_configure_wb(isp_dev); +} + +static void +sun6i_isp_params_configure_bdnf(struct sun6i_isp_device *isp_dev, + const struct sun6i_isp_params_config *config) +{ + const struct sun6i_isp_params_config_bdnf *bdnf = &config->bdnf; + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_CFG_REG, + SUN6I_ISP_BDNF_CFG_IN_DIS_MIN(bdnf->in_dis_min) | + SUN6I_ISP_BDNF_CFG_IN_DIS_MAX(bdnf->in_dis_max)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_COEF_RB_REG, + SUN6I_ISP_BDNF_COEF_RB(0, bdnf->coefficients_rb[0]) | + SUN6I_ISP_BDNF_COEF_RB(1, bdnf->coefficients_rb[1]) | + SUN6I_ISP_BDNF_COEF_RB(2, bdnf->coefficients_rb[2]) | + SUN6I_ISP_BDNF_COEF_RB(3, bdnf->coefficients_rb[3]) | + SUN6I_ISP_BDNF_COEF_RB(4, bdnf->coefficients_rb[4])); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_COEF_G_REG, + SUN6I_ISP_BDNF_COEF_G(0, bdnf->coefficients_g[0]) | + SUN6I_ISP_BDNF_COEF_G(1, bdnf->coefficients_g[1]) | + SUN6I_ISP_BDNF_COEF_G(2, bdnf->coefficients_g[2]) | + SUN6I_ISP_BDNF_COEF_G(3, bdnf->coefficients_g[3]) | + SUN6I_ISP_BDNF_COEF_G(4, bdnf->coefficients_g[4]) | + SUN6I_ISP_BDNF_COEF_G(5, bdnf->coefficients_g[5]) | + SUN6I_ISP_BDNF_COEF_G(6, bdnf->coefficients_g[6])); +} + +static void +sun6i_isp_params_configure_modules(struct sun6i_isp_device *isp_dev, + const struct sun6i_isp_params_config *config) +{ + u32 value; + + if (config->modules_used & SUN6I_ISP_MODULE_BDNF) + sun6i_isp_params_configure_bdnf(isp_dev, config); + + if (config->modules_used & SUN6I_ISP_MODULE_BAYER) + sun6i_isp_params_configure_bayer(isp_dev, config); + + value = sun6i_isp_load_read(isp_dev, SUN6I_ISP_MODULE_EN_REG); + /* Clear all modules but keep input configuration. */ + value &= SUN6I_ISP_MODULE_EN_SRC0 | SUN6I_ISP_MODULE_EN_SRC1; + + if (config->modules_used & SUN6I_ISP_MODULE_BDNF) + value |= SUN6I_ISP_MODULE_EN_BDNF; + + /* Bayer stage is always enabled. */ + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MODULE_EN_REG, value); +} + +void sun6i_isp_params_configure(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + sun6i_isp_params_configure_base(isp_dev); + + /* Default config is only applied at the very first stream start. */ + if (state->configured) + goto complete; + + sun6i_isp_params_configure_modules(isp_dev, + &sun6i_isp_params_config_default); + + state->configured = true; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +/* State */ + +static void sun6i_isp_params_state_cleanup(struct sun6i_isp_device *isp_dev, + bool error) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct sun6i_isp_buffer *isp_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (state->pending) { + vb2_buffer = &state->pending->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + list_for_each_entry(isp_buffer, &state->queue, list) { + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + INIT_LIST_HEAD(&state->queue); + + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_isp_params_state_update(struct sun6i_isp_device *isp_dev, + bool *update) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct sun6i_isp_buffer *isp_buffer; + struct vb2_buffer *vb2_buffer; + const struct sun6i_isp_params_config *config; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (list_empty(&state->queue)) + goto complete; + + if (state->pending) + goto complete; + + isp_buffer = list_first_entry(&state->queue, struct sun6i_isp_buffer, + list); + + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + config = vb2_plane_vaddr(vb2_buffer, 0); + + sun6i_isp_params_configure_modules(isp_dev, config); + + list_del(&isp_buffer->list); + + state->pending = isp_buffer; + + if (update) + *update = true; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_isp_params_state_complete(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct sun6i_isp_buffer *isp_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (!state->pending) + goto complete; + + isp_buffer = state->pending; + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + + vb2_buffer->timestamp = ktime_get_ns(); + + /* Parameters will be applied starting from the next frame. */ + isp_buffer->v4l2_buffer.sequence = isp_dev->capture.state.sequence + 1; + + vb2_buffer_done(vb2_buffer, VB2_BUF_STATE_DONE); + + state->pending = NULL; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +/* Queue */ + +static int sun6i_isp_params_queue_setup(struct vb2_queue *queue, + unsigned int *buffers_count, + unsigned int *planes_count, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + unsigned int size = isp_dev->params.format.fmt.meta.buffersize; + + if (*planes_count) + return sizes[0] < size ? -EINVAL : 0; + + *planes_count = 1; + sizes[0] = size; + + return 0; +} + +static int sun6i_isp_params_buffer_prepare(struct vb2_buffer *vb2_buffer) +{ + struct sun6i_isp_device *isp_dev = + vb2_get_drv_priv(vb2_buffer->vb2_queue); + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + unsigned int size = isp_dev->params.format.fmt.meta.buffersize; + + if (vb2_plane_size(vb2_buffer, 0) < size) { + v4l2_err(v4l2_dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb2_buffer, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb2_buffer, 0, size); + + return 0; +} + +static void sun6i_isp_params_buffer_queue(struct vb2_buffer *vb2_buffer) +{ + struct sun6i_isp_device *isp_dev = + vb2_get_drv_priv(vb2_buffer->vb2_queue); + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(vb2_buffer); + struct sun6i_isp_buffer *isp_buffer = + container_of(v4l2_buffer, struct sun6i_isp_buffer, v4l2_buffer); + bool capture_streaming = isp_dev->capture.state.streaming; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + list_add_tail(&isp_buffer->list, &state->queue); + spin_unlock_irqrestore(&state->lock, flags); + + if (state->streaming && capture_streaming) + sun6i_isp_state_update(isp_dev, false); +} + +static int sun6i_isp_params_start_streaming(struct vb2_queue *queue, + unsigned int count) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + struct sun6i_isp_params_state *state = &isp_dev->params.state; + bool capture_streaming = isp_dev->capture.state.streaming; + + state->streaming = true; + + /* + * Update the state as soon as possible if capture is streaming, + * otherwise it will be applied when capture starts streaming. + */ + + if (capture_streaming) + sun6i_isp_state_update(isp_dev, false); + + return 0; +} + +static void sun6i_isp_params_stop_streaming(struct vb2_queue *queue) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + struct sun6i_isp_params_state *state = &isp_dev->params.state; + + state->streaming = false; + sun6i_isp_params_state_cleanup(isp_dev, true); +} + +static const struct vb2_ops sun6i_isp_params_queue_ops = { + .queue_setup = sun6i_isp_params_queue_setup, + .buf_prepare = sun6i_isp_params_buffer_prepare, + .buf_queue = sun6i_isp_params_buffer_queue, + .start_streaming = sun6i_isp_params_start_streaming, + .stop_streaming = sun6i_isp_params_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* Video Device */ + +static int sun6i_isp_params_querycap(struct file *file, void *private, + struct v4l2_capability *capability) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + struct video_device *video_dev = &isp_dev->params.video_dev; + + strscpy(capability->driver, SUN6I_ISP_NAME, sizeof(capability->driver)); + strscpy(capability->card, video_dev->name, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", dev_name(isp_dev->dev)); + + return 0; +} + +static int sun6i_isp_params_enum_fmt(struct file *file, void *private, + struct v4l2_fmtdesc *fmtdesc) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + struct v4l2_meta_format *params_format = + &isp_dev->params.format.fmt.meta; + + if (fmtdesc->index > 0) + return -EINVAL; + + fmtdesc->pixelformat = params_format->dataformat; + + return 0; +} + +static int sun6i_isp_params_g_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + + *format = isp_dev->params.format; + + return 0; +} + +static const struct v4l2_ioctl_ops sun6i_isp_params_ioctl_ops = { + .vidioc_querycap = sun6i_isp_params_querycap, + + .vidioc_enum_fmt_meta_out = sun6i_isp_params_enum_fmt, + .vidioc_g_fmt_meta_out = sun6i_isp_params_g_fmt, + .vidioc_s_fmt_meta_out = sun6i_isp_params_g_fmt, + .vidioc_try_fmt_meta_out = sun6i_isp_params_g_fmt, + + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct v4l2_file_operations sun6i_isp_params_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +/* Params */ + +int sun6i_isp_params_setup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params *params = &isp_dev->params; + struct sun6i_isp_params_state *state = ¶ms->state; + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + struct v4l2_subdev *proc_subdev = &isp_dev->proc.subdev; + struct video_device *video_dev = ¶ms->video_dev; + struct vb2_queue *queue = &isp_dev->params.queue; + struct media_pad *pad = &isp_dev->params.pad; + struct v4l2_format *format = &isp_dev->params.format; + struct v4l2_meta_format *params_format = &format->fmt.meta; + int ret; + + /* State */ + + INIT_LIST_HEAD(&state->queue); + spin_lock_init(&state->lock); + + /* Media Pads */ + + pad->flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&video_dev->entity, 1, pad); + if (ret) + goto error_mutex; + + /* Queue */ + + mutex_init(¶ms->lock); + + queue->type = V4L2_BUF_TYPE_META_OUTPUT; + queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + queue->buf_struct_size = sizeof(struct sun6i_isp_buffer); + queue->ops = &sun6i_isp_params_queue_ops; + queue->mem_ops = &vb2_vmalloc_memops; + queue->min_buffers_needed = 1; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->lock = ¶ms->lock; + queue->dev = isp_dev->dev; + queue->drv_priv = isp_dev; + + ret = vb2_queue_init(queue); + if (ret) { + v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Format */ + + format->type = queue->type; + params_format->dataformat = V4L2_META_FMT_SUN6I_ISP_PARAMS; + params_format->buffersize = sizeof(struct sun6i_isp_params_config); + + /* Video Device */ + + strscpy(video_dev->name, SUN6I_ISP_PARAMS_NAME, + sizeof(video_dev->name)); + video_dev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING; + video_dev->vfl_dir = VFL_DIR_TX; + video_dev->release = video_device_release_empty; + video_dev->fops = &sun6i_isp_params_fops; + video_dev->ioctl_ops = &sun6i_isp_params_ioctl_ops; + video_dev->v4l2_dev = v4l2_dev; + video_dev->queue = queue; + video_dev->lock = ¶ms->lock; + + video_set_drvdata(video_dev, isp_dev); + + ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(v4l2_dev, "failed to register video device: %d\n", + ret); + goto error_media_entity; + } + + /* Media Pad Link */ + + ret = media_create_pad_link(&video_dev->entity, 0, + &proc_subdev->entity, + SUN6I_ISP_PROC_PAD_SINK_PARAMS, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n", + video_dev->entity.name, 0, proc_subdev->entity.name, + SUN6I_ISP_PROC_PAD_SINK_PARAMS); + goto error_video_device; + } + + return 0; + +error_video_device: + vb2_video_unregister_device(video_dev); + +error_media_entity: + media_entity_cleanup(&video_dev->entity); + +error_mutex: + mutex_destroy(¶ms->lock); + + return ret; +} + +void sun6i_isp_params_cleanup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params *params = &isp_dev->params; + struct video_device *video_dev = ¶ms->video_dev; + + vb2_video_unregister_device(video_dev); + media_entity_cleanup(&video_dev->entity); + mutex_destroy(¶ms->lock); +} diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.h new file mode 100644 index 000000000000..50f10f879c42 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_ISP_PARAMS_H_ +#define _SUN6I_ISP_PARAMS_H_ + +#include <media/v4l2-device.h> + +#define SUN6I_ISP_PARAMS_NAME "sun6i-isp-params" + +struct sun6i_isp_device; + +struct sun6i_isp_params_state { + struct list_head queue; /* Queue and buffers lock. */ + spinlock_t lock; + + struct sun6i_isp_buffer *pending; + + bool configured; + bool streaming; +}; + +struct sun6i_isp_params { + struct sun6i_isp_params_state state; + + struct video_device video_dev; + struct vb2_queue queue; + struct mutex lock; /* Queue lock. */ + struct media_pad pad; + + struct v4l2_format format; +}; + +/* Params */ + +void sun6i_isp_params_configure(struct sun6i_isp_device *isp_dev); + +/* State */ + +void sun6i_isp_params_state_update(struct sun6i_isp_device *isp_dev, + bool *update); +void sun6i_isp_params_state_complete(struct sun6i_isp_device *isp_dev); + +/* Params */ + +int sun6i_isp_params_setup(struct sun6i_isp_device *isp_dev); +void sun6i_isp_params_cleanup(struct sun6i_isp_device *isp_dev); + +#endif diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c new file mode 100644 index 000000000000..d69d2be0add2 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#include "sun6i_isp.h" +#include "sun6i_isp_capture.h" +#include "sun6i_isp_params.h" +#include "sun6i_isp_proc.h" +#include "sun6i_isp_reg.h" + +/* Helpers */ + +void sun6i_isp_proc_dimensions(struct sun6i_isp_device *isp_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = isp_dev->proc.mbus_format.width; + if (height) + *height = isp_dev->proc.mbus_format.height; +} + +/* Format */ + +static const struct sun6i_isp_proc_format sun6i_isp_proc_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_BGGR, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_GBRG, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_GRBG, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_RGGB, + }, + + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_BGGR, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_GBRG, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_GRBG, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .input_format = SUN6I_ISP_INPUT_FMT_RAW_RGGB, + }, +}; + +const struct sun6i_isp_proc_format *sun6i_isp_proc_format_find(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_isp_proc_formats); i++) + if (sun6i_isp_proc_formats[i].mbus_code == mbus_code) + return &sun6i_isp_proc_formats[i]; + + return NULL; +} + +/* Processor */ + +static void sun6i_isp_proc_irq_enable(struct sun6i_isp_device *isp_dev) +{ + struct regmap *regmap = isp_dev->regmap; + + regmap_write(regmap, SUN6I_ISP_FE_INT_EN_REG, + SUN6I_ISP_FE_INT_EN_FINISH | + SUN6I_ISP_FE_INT_EN_START | + SUN6I_ISP_FE_INT_EN_PARA_SAVE | + SUN6I_ISP_FE_INT_EN_PARA_LOAD | + SUN6I_ISP_FE_INT_EN_SRC0_FIFO | + SUN6I_ISP_FE_INT_EN_ROT_FINISH); +} + +static void sun6i_isp_proc_irq_disable(struct sun6i_isp_device *isp_dev) +{ + struct regmap *regmap = isp_dev->regmap; + + regmap_write(regmap, SUN6I_ISP_FE_INT_EN_REG, 0); +} + +static void sun6i_isp_proc_irq_clear(struct sun6i_isp_device *isp_dev) +{ + struct regmap *regmap = isp_dev->regmap; + + regmap_write(regmap, SUN6I_ISP_FE_INT_EN_REG, 0); + regmap_write(regmap, SUN6I_ISP_FE_INT_STA_REG, + SUN6I_ISP_FE_INT_STA_CLEAR); +} + +static void sun6i_isp_proc_enable(struct sun6i_isp_device *isp_dev, + struct sun6i_isp_proc_source *source) +{ + struct sun6i_isp_proc *proc = &isp_dev->proc; + struct regmap *regmap = isp_dev->regmap; + u8 mode; + + /* Frontend */ + + if (source == &proc->source_csi0) + mode = SUN6I_ISP_SRC_MODE_CSI(0); + else + mode = SUN6I_ISP_SRC_MODE_CSI(1); + + regmap_write(regmap, SUN6I_ISP_FE_CFG_REG, + SUN6I_ISP_FE_CFG_EN | SUN6I_ISP_FE_CFG_SRC0_MODE(mode)); + + regmap_write(regmap, SUN6I_ISP_FE_CTRL_REG, + SUN6I_ISP_FE_CTRL_VCAP_EN | SUN6I_ISP_FE_CTRL_PARA_READY); +} + +static void sun6i_isp_proc_disable(struct sun6i_isp_device *isp_dev) +{ + struct regmap *regmap = isp_dev->regmap; + + /* Frontend */ + + regmap_write(regmap, SUN6I_ISP_FE_CTRL_REG, 0); + regmap_write(regmap, SUN6I_ISP_FE_CFG_REG, 0); +} + +static void sun6i_isp_proc_configure(struct sun6i_isp_device *isp_dev) +{ + struct v4l2_mbus_framefmt *mbus_format = &isp_dev->proc.mbus_format; + const struct sun6i_isp_proc_format *format; + u32 value; + + /* Module */ + + value = sun6i_isp_load_read(isp_dev, SUN6I_ISP_MODULE_EN_REG); + value |= SUN6I_ISP_MODULE_EN_SRC0; + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MODULE_EN_REG, value); + + /* Input */ + + format = sun6i_isp_proc_format_find(mbus_format->code); + if (WARN_ON(!format)) + return; + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MODE_REG, + SUN6I_ISP_MODE_INPUT_FMT(format->input_format) | + SUN6I_ISP_MODE_INPUT_YUV_SEQ(format->input_yuv_seq) | + SUN6I_ISP_MODE_SHARP(1) | + SUN6I_ISP_MODE_HIST(2)); +} + +/* V4L2 Subdev */ + +static int sun6i_isp_proc_s_stream(struct v4l2_subdev *subdev, int on) +{ + struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev); + struct sun6i_isp_proc *proc = &isp_dev->proc; + struct media_pad *local_pad = &proc->pads[SUN6I_ISP_PROC_PAD_SINK_CSI]; + struct device *dev = isp_dev->dev; + struct sun6i_isp_proc_source *source; + struct v4l2_subdev *source_subdev; + struct media_pad *remote_pad; + /* Initialize to 0 to use both in disable label (ret != 0) and off. */ + int ret = 0; + + /* Source */ + + remote_pad = media_pad_remote_pad_unique(local_pad); + if (IS_ERR(remote_pad)) { + dev_err(dev, + "zero or more than a single source connected to the bridge\n"); + return PTR_ERR(remote_pad); + } + + source_subdev = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (source_subdev == proc->source_csi0.subdev) + source = &proc->source_csi0; + else + source = &proc->source_csi1; + + if (!on) { + sun6i_isp_proc_irq_disable(isp_dev); + v4l2_subdev_call(source_subdev, video, s_stream, 0); + goto disable; + } + + /* PM */ + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Clear */ + + sun6i_isp_proc_irq_clear(isp_dev); + + /* Configure */ + + sun6i_isp_tables_configure(isp_dev); + sun6i_isp_params_configure(isp_dev); + sun6i_isp_proc_configure(isp_dev); + sun6i_isp_capture_configure(isp_dev); + + /* State Update */ + + sun6i_isp_state_update(isp_dev, true); + + /* Enable */ + + sun6i_isp_proc_irq_enable(isp_dev); + sun6i_isp_proc_enable(isp_dev, source); + + ret = v4l2_subdev_call(source_subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) { + sun6i_isp_proc_irq_disable(isp_dev); + goto disable; + } + + return 0; + +disable: + sun6i_isp_proc_disable(isp_dev); + + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops sun6i_isp_proc_video_ops = { + .s_stream = sun6i_isp_proc_s_stream, +}; + +static void +sun6i_isp_proc_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) +{ + if (!sun6i_isp_proc_format_find(mbus_format->code)) + mbus_format->code = sun6i_isp_proc_formats[0].mbus_code; + + mbus_format->field = V4L2_FIELD_NONE; + mbus_format->colorspace = V4L2_COLORSPACE_RAW; + mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT; + mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_isp_proc_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state) +{ + struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev); + unsigned int pad = SUN6I_ISP_PROC_PAD_SINK_CSI; + struct v4l2_mbus_framefmt *mbus_format = + v4l2_subdev_get_try_format(subdev, state, pad); + struct mutex *lock = &isp_dev->proc.lock; + + mutex_lock(lock); + + mbus_format->code = sun6i_isp_proc_formats[0].mbus_code; + mbus_format->width = 1280; + mbus_format->height = 720; + + sun6i_isp_proc_mbus_format_prepare(mbus_format); + + mutex_unlock(lock); + + return 0; +} + +static int +sun6i_isp_proc_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code_enum) +{ + if (code_enum->index >= ARRAY_SIZE(sun6i_isp_proc_formats)) + return -EINVAL; + + code_enum->code = sun6i_isp_proc_formats[code_enum->index].mbus_code; + + return 0; +} + +static int sun6i_isp_proc_get_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &isp_dev->proc.lock; + + mutex_lock(lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *mbus_format = *v4l2_subdev_get_try_format(subdev, state, + format->pad); + else + *mbus_format = isp_dev->proc.mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static int sun6i_isp_proc_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &isp_dev->proc.lock; + + mutex_lock(lock); + + sun6i_isp_proc_mbus_format_prepare(mbus_format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(subdev, state, format->pad) = + *mbus_format; + else + isp_dev->proc.mbus_format = *mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops sun6i_isp_proc_pad_ops = { + .init_cfg = sun6i_isp_proc_init_cfg, + .enum_mbus_code = sun6i_isp_proc_enum_mbus_code, + .get_fmt = sun6i_isp_proc_get_fmt, + .set_fmt = sun6i_isp_proc_set_fmt, +}; + +const struct v4l2_subdev_ops sun6i_isp_proc_subdev_ops = { + .video = &sun6i_isp_proc_video_ops, + .pad = &sun6i_isp_proc_pad_ops, +}; + +/* Media Entity */ + +static const struct media_entity_operations sun6i_isp_proc_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* V4L2 Async */ + +static int sun6i_isp_proc_link(struct sun6i_isp_device *isp_dev, + int sink_pad_index, + struct v4l2_subdev *remote_subdev, bool enabled) +{ + struct device *dev = isp_dev->dev; + struct v4l2_subdev *subdev = &isp_dev->proc.subdev; + struct media_entity *sink_entity = &subdev->entity; + struct media_entity *source_entity = &remote_subdev->entity; + int source_pad_index; + int ret; + + /* Get the first remote source pad. */ + ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "missing source pad in external entity %s\n", + source_entity->name); + return -EINVAL; + } + + source_pad_index = ret; + + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name, + source_pad_index, sink_entity->name, sink_pad_index); + + ret = media_create_pad_link(source_entity, source_pad_index, + sink_entity, sink_pad_index, + enabled ? MEDIA_LNK_FL_ENABLED : 0); + if (ret < 0) { + dev_err(dev, "failed to create %s:%u -> %s:%u link\n", + source_entity->name, source_pad_index, + sink_entity->name, sink_pad_index); + return ret; + } + + return 0; +} + +static int sun6i_isp_proc_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *remote_subdev, + struct v4l2_async_subdev *async_subdev) +{ + struct sun6i_isp_device *isp_dev = + container_of(notifier, struct sun6i_isp_device, proc.notifier); + struct sun6i_isp_proc_async_subdev *proc_async_subdev = + container_of(async_subdev, struct sun6i_isp_proc_async_subdev, + async_subdev); + struct sun6i_isp_proc *proc = &isp_dev->proc; + struct sun6i_isp_proc_source *source = proc_async_subdev->source; + bool enabled; + + switch (source->endpoint.base.port) { + case SUN6I_ISP_PORT_CSI0: + source = &proc->source_csi0; + enabled = true; + break; + case SUN6I_ISP_PORT_CSI1: + source = &proc->source_csi1; + enabled = !proc->source_csi0.expected; + break; + default: + break; + } + + source->subdev = remote_subdev; + + return sun6i_isp_proc_link(isp_dev, SUN6I_ISP_PROC_PAD_SINK_CSI, + remote_subdev, enabled); +} + +static int +sun6i_isp_proc_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct sun6i_isp_device *isp_dev = + container_of(notifier, struct sun6i_isp_device, proc.notifier); + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + int ret; + + ret = v4l2_device_register_subdev_nodes(v4l2_dev); + if (ret) + return ret; + + return 0; +} + +static const struct v4l2_async_notifier_operations +sun6i_isp_proc_notifier_ops = { + .bound = sun6i_isp_proc_notifier_bound, + .complete = sun6i_isp_proc_notifier_complete, +}; + +/* Processor */ + +static int sun6i_isp_proc_source_setup(struct sun6i_isp_device *isp_dev, + struct sun6i_isp_proc_source *source, + u32 port) +{ + struct device *dev = isp_dev->dev; + struct v4l2_async_notifier *notifier = &isp_dev->proc.notifier; + struct v4l2_fwnode_endpoint *endpoint = &source->endpoint; + struct sun6i_isp_proc_async_subdev *proc_async_subdev; + struct fwnode_handle *handle = NULL; + int ret; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), port, 0, 0); + if (!handle) + return -ENODEV; + + ret = v4l2_fwnode_endpoint_parse(handle, endpoint); + if (ret) + goto complete; + + proc_async_subdev = + v4l2_async_nf_add_fwnode_remote(notifier, handle, + struct + sun6i_isp_proc_async_subdev); + if (IS_ERR(proc_async_subdev)) { + ret = PTR_ERR(proc_async_subdev); + goto complete; + } + + proc_async_subdev->source = source; + + source->expected = true; + +complete: + fwnode_handle_put(handle); + + return ret; +} + +int sun6i_isp_proc_setup(struct sun6i_isp_device *isp_dev) +{ + struct device *dev = isp_dev->dev; + struct sun6i_isp_proc *proc = &isp_dev->proc; + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + struct v4l2_async_notifier *notifier = &proc->notifier; + struct v4l2_subdev *subdev = &proc->subdev; + struct media_pad *pads = proc->pads; + int ret; + + mutex_init(&proc->lock); + + /* V4L2 Subdev */ + + v4l2_subdev_init(subdev, &sun6i_isp_proc_subdev_ops); + strscpy(subdev->name, SUN6I_ISP_PROC_NAME, sizeof(subdev->name)); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->owner = THIS_MODULE; + subdev->dev = dev; + + v4l2_set_subdevdata(subdev, isp_dev); + + /* Media Entity */ + + subdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP; + subdev->entity.ops = &sun6i_isp_proc_entity_ops; + + /* Media Pads */ + + pads[SUN6I_ISP_PROC_PAD_SINK_CSI].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN6I_ISP_PROC_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN6I_ISP_PROC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&subdev->entity, SUN6I_ISP_PROC_PAD_COUNT, + pads); + if (ret) + return ret; + + /* V4L2 Subdev */ + + ret = v4l2_device_register_subdev(v4l2_dev, subdev); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to register v4l2 subdev: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Async */ + + v4l2_async_nf_init(notifier); + notifier->ops = &sun6i_isp_proc_notifier_ops; + + sun6i_isp_proc_source_setup(isp_dev, &proc->source_csi0, + SUN6I_ISP_PORT_CSI0); + sun6i_isp_proc_source_setup(isp_dev, &proc->source_csi1, + SUN6I_ISP_PORT_CSI1); + + ret = v4l2_async_nf_register(v4l2_dev, notifier); + if (ret) { + v4l2_err(v4l2_dev, + "failed to register v4l2 async notifier: %d\n", ret); + goto error_v4l2_async_notifier; + } + + return 0; + +error_v4l2_async_notifier: + v4l2_async_nf_cleanup(notifier); + + v4l2_device_unregister_subdev(subdev); + +error_media_entity: + media_entity_cleanup(&subdev->entity); + + return ret; +} + +void sun6i_isp_proc_cleanup(struct sun6i_isp_device *isp_dev) +{ + struct v4l2_async_notifier *notifier = &isp_dev->proc.notifier; + struct v4l2_subdev *subdev = &isp_dev->proc.subdev; + + v4l2_async_nf_unregister(notifier); + v4l2_async_nf_cleanup(notifier); + + v4l2_device_unregister_subdev(subdev); + media_entity_cleanup(&subdev->entity); +} diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.h new file mode 100644 index 000000000000..c5c274e21ad5 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_ISP_PROC_H_ +#define _SUN6I_ISP_PROC_H_ + +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define SUN6I_ISP_PROC_NAME "sun6i-isp-proc" + +enum sun6i_isp_proc_pad { + SUN6I_ISP_PROC_PAD_SINK_CSI = 0, + SUN6I_ISP_PROC_PAD_SINK_PARAMS = 1, + SUN6I_ISP_PROC_PAD_SOURCE = 2, + SUN6I_ISP_PROC_PAD_COUNT = 3, +}; + +struct sun6i_isp_device; + +struct sun6i_isp_proc_format { + u32 mbus_code; + u8 input_format; + u8 input_yuv_seq; +}; + +struct sun6i_isp_proc_source { + struct v4l2_subdev *subdev; + struct v4l2_fwnode_endpoint endpoint; + bool expected; +}; + +struct sun6i_isp_proc_async_subdev { + struct v4l2_async_subdev async_subdev; + struct sun6i_isp_proc_source *source; +}; + +struct sun6i_isp_proc { + struct v4l2_subdev subdev; + struct media_pad pads[3]; + struct v4l2_async_notifier notifier; + struct v4l2_mbus_framefmt mbus_format; + struct mutex lock; /* Mbus format lock. */ + + struct sun6i_isp_proc_source source_csi0; + struct sun6i_isp_proc_source source_csi1; +}; + +/* Helpers */ + +void sun6i_isp_proc_dimensions(struct sun6i_isp_device *isp_dev, + unsigned int *width, unsigned int *height); + +/* Format */ + +const struct sun6i_isp_proc_format *sun6i_isp_proc_format_find(u32 mbus_code); + +/* Proc */ + +int sun6i_isp_proc_setup(struct sun6i_isp_device *isp_dev); +void sun6i_isp_proc_cleanup(struct sun6i_isp_device *isp_dev); + +#endif diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_reg.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_reg.h new file mode 100644 index 000000000000..83b9cdab2134 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_reg.h @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_ISP_REG_H_ +#define _SUN6I_ISP_REG_H_ + +#include <linux/kernel.h> + +#define SUN6I_ISP_ADDR_VALUE(a) ((a) >> 2) + +/* Frontend */ + +#define SUN6I_ISP_SRC_MODE_DRAM 0 +#define SUN6I_ISP_SRC_MODE_CSI(n) (1 + (n)) + +#define SUN6I_ISP_FE_CFG_REG 0x0 +#define SUN6I_ISP_FE_CFG_EN BIT(0) +#define SUN6I_ISP_FE_CFG_SRC0_MODE(v) (((v) << 8) & GENMASK(9, 8)) +#define SUN6I_ISP_FE_CFG_SRC1_MODE(v) (((v) << 16) & GENMASK(17, 16)) + +#define SUN6I_ISP_FE_CTRL_REG 0x4 +#define SUN6I_ISP_FE_CTRL_SCAP_EN BIT(0) +#define SUN6I_ISP_FE_CTRL_VCAP_EN BIT(1) +#define SUN6I_ISP_FE_CTRL_PARA_READY BIT(2) +#define SUN6I_ISP_FE_CTRL_LUT_UPDATE BIT(3) +#define SUN6I_ISP_FE_CTRL_LENS_UPDATE BIT(4) +#define SUN6I_ISP_FE_CTRL_GAMMA_UPDATE BIT(5) +#define SUN6I_ISP_FE_CTRL_DRC_UPDATE BIT(6) +#define SUN6I_ISP_FE_CTRL_DISC_UPDATE BIT(7) +#define SUN6I_ISP_FE_CTRL_OUTPUT_SPEED_CTRL(v) (((v) << 16) & GENMASK(17, 16)) +#define SUN6I_ISP_FE_CTRL_VCAP_READ_START BIT(31) + +#define SUN6I_ISP_FE_INT_EN_REG 0x8 +#define SUN6I_ISP_FE_INT_EN_FINISH BIT(0) +#define SUN6I_ISP_FE_INT_EN_START BIT(1) +#define SUN6I_ISP_FE_INT_EN_PARA_SAVE BIT(2) +#define SUN6I_ISP_FE_INT_EN_PARA_LOAD BIT(3) +#define SUN6I_ISP_FE_INT_EN_SRC0_FIFO BIT(4) +#define SUN6I_ISP_FE_INT_EN_SRC1_FIFO BIT(5) +#define SUN6I_ISP_FE_INT_EN_ROT_FINISH BIT(6) +#define SUN6I_ISP_FE_INT_EN_LINE_NUM_START BIT(7) + +#define SUN6I_ISP_FE_INT_STA_REG 0xc +#define SUN6I_ISP_FE_INT_STA_CLEAR 0xff +#define SUN6I_ISP_FE_INT_STA_FINISH BIT(0) +#define SUN6I_ISP_FE_INT_STA_START BIT(1) +#define SUN6I_ISP_FE_INT_STA_PARA_SAVE BIT(2) +#define SUN6I_ISP_FE_INT_STA_PARA_LOAD BIT(3) +#define SUN6I_ISP_FE_INT_STA_SRC0_FIFO BIT(4) +#define SUN6I_ISP_FE_INT_STA_SRC1_FIFO BIT(5) +#define SUN6I_ISP_FE_INT_STA_ROT_FINISH BIT(6) +#define SUN6I_ISP_FE_INT_STA_LINE_NUM_START BIT(7) + +/* Only since sun9i-a80-isp. */ +#define SUN6I_ISP_FE_INT_LINE_NUM_REG 0x18 +#define SUN6I_ISP_FE_ROT_OF_CFG_REG 0x1c + +/* Buffers/tables */ + +#define SUN6I_ISP_REG_LOAD_ADDR_REG 0x20 +#define SUN6I_ISP_REG_SAVE_ADDR_REG 0x24 + +#define SUN6I_ISP_LUT_TABLE_ADDR_REG 0x28 +#define SUN6I_ISP_DRC_TABLE_ADDR_REG 0x2c +#define SUN6I_ISP_STATS_ADDR_REG 0x30 + +/* SRAM */ + +#define SUN6I_ISP_SRAM_RW_OFFSET_REG 0x38 +#define SUN6I_ISP_SRAM_RW_DATA_REG 0x3c + +/* Global */ + +#define SUN6I_ISP_MODULE_EN_REG 0x40 +#define SUN6I_ISP_MODULE_EN_AE BIT(0) +#define SUN6I_ISP_MODULE_EN_OBC BIT(1) +#define SUN6I_ISP_MODULE_EN_DPC_LUT BIT(2) +#define SUN6I_ISP_MODULE_EN_DPC_OTF BIT(3) +#define SUN6I_ISP_MODULE_EN_BDNF BIT(4) +#define SUN6I_ISP_MODULE_EN_AWB BIT(6) +#define SUN6I_ISP_MODULE_EN_WB BIT(7) +#define SUN6I_ISP_MODULE_EN_LSC BIT(8) +#define SUN6I_ISP_MODULE_EN_BGC BIT(9) +#define SUN6I_ISP_MODULE_EN_SAP BIT(10) +#define SUN6I_ISP_MODULE_EN_AF BIT(11) +#define SUN6I_ISP_MODULE_EN_RGB2RGB BIT(12) +#define SUN6I_ISP_MODULE_EN_RGB_DRC BIT(13) +#define SUN6I_ISP_MODULE_EN_TDNF BIT(15) +#define SUN6I_ISP_MODULE_EN_AFS BIT(16) +#define SUN6I_ISP_MODULE_EN_HIST BIT(17) +#define SUN6I_ISP_MODULE_EN_YUV_GAIN_OFFSET BIT(18) +#define SUN6I_ISP_MODULE_EN_YUV_DRC BIT(19) +#define SUN6I_ISP_MODULE_EN_TG BIT(20) +#define SUN6I_ISP_MODULE_EN_ROT BIT(21) +#define SUN6I_ISP_MODULE_EN_CONTRAST BIT(22) +#define SUN6I_ISP_MODULE_EN_SATU BIT(24) +#define SUN6I_ISP_MODULE_EN_SRC1 BIT(30) +#define SUN6I_ISP_MODULE_EN_SRC0 BIT(31) + +#define SUN6I_ISP_MODE_REG 0x44 +#define SUN6I_ISP_MODE_INPUT_FMT(v) ((v) & GENMASK(2, 0)) +#define SUN6I_ISP_MODE_INPUT_YUV_SEQ(v) (((v) << 3) & GENMASK(4, 3)) +#define SUN6I_ISP_MODE_OTF_DPC(v) (((v) << 16) & BIT(16)) +#define SUN6I_ISP_MODE_SHARP(v) (((v) << 17) & BIT(17)) +#define SUN6I_ISP_MODE_HIST(v) (((v) << 20) & GENMASK(21, 20)) + +#define SUN6I_ISP_INPUT_FMT_YUV420 0 +#define SUN6I_ISP_INPUT_FMT_YUV422 1 +#define SUN6I_ISP_INPUT_FMT_RAW_BGGR 4 +#define SUN6I_ISP_INPUT_FMT_RAW_RGGB 5 +#define SUN6I_ISP_INPUT_FMT_RAW_GBRG 6 +#define SUN6I_ISP_INPUT_FMT_RAW_GRBG 7 + +#define SUN6I_ISP_INPUT_YUV_SEQ_YUYV 0 +#define SUN6I_ISP_INPUT_YUV_SEQ_YVYU 1 +#define SUN6I_ISP_INPUT_YUV_SEQ_UYVY 2 +#define SUN6I_ISP_INPUT_YUV_SEQ_VYUY 3 + +#define SUN6I_ISP_IN_CFG_REG 0x48 +#define SUN6I_ISP_IN_CFG_STRIDE_DIV16(v) ((v) & GENMASK(10, 0)) + +#define SUN6I_ISP_IN_LUMA_RGB_ADDR0_REG 0x4c +#define SUN6I_ISP_IN_CHROMA_ADDR0_REG 0x50 +#define SUN6I_ISP_IN_LUMA_RGB_ADDR1_REG 0x54 +#define SUN6I_ISP_IN_CHROMA_ADDR1_REG 0x58 + +/* AE */ + +#define SUN6I_ISP_AE_CFG_REG 0x60 +#define SUN6I_ISP_AE_CFG_LOW_BRI_TH(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_AE_CFG_HORZ_NUM(v) (((v) << 12) & GENMASK(15, 12)) +#define SUN6I_ISP_AE_CFG_HIGH_BRI_TH(v) (((v) << 16) & GENMASK(27, 16)) +#define SUN6I_ISP_AE_CFG_VERT_NUM(v) (((v) << 28) & GENMASK(31, 28)) + +#define SUN6I_ISP_AE_SIZE_REG 0x64 +#define SUN6I_ISP_AE_SIZE_WIDTH(v) ((v) & GENMASK(10, 0)) +#define SUN6I_ISP_AE_SIZE_HEIGHT(v) (((v) << 16) & GENMASK(26, 16)) + +#define SUN6I_ISP_AE_POS_REG 0x68 +#define SUN6I_ISP_AE_POS_HORZ_START(v) ((v) & GENMASK(10, 0)) +#define SUN6I_ISP_AE_POS_VERT_START(v) (((v) << 16) & GENMASK(26, 16)) + +/* OB */ + +#define SUN6I_ISP_OB_SIZE_REG 0x78 +#define SUN6I_ISP_OB_SIZE_WIDTH(v) ((v) & GENMASK(13, 0)) +#define SUN6I_ISP_OB_SIZE_HEIGHT(v) (((v) << 16) & GENMASK(29, 16)) + +#define SUN6I_ISP_OB_VALID_REG 0x7c +#define SUN6I_ISP_OB_VALID_WIDTH(v) ((v) & GENMASK(12, 0)) +#define SUN6I_ISP_OB_VALID_HEIGHT(v) (((v) << 16) & GENMASK(28, 16)) + +#define SUN6I_ISP_OB_SRC0_VALID_START_REG 0x80 +#define SUN6I_ISP_OB_SRC0_VALID_START_HORZ(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_OB_SRC0_VALID_START_VERT(v) (((v) << 16) & GENMASK(27, 16)) + +#define SUN6I_ISP_OB_SRC1_VALID_START_REG 0x84 +#define SUN6I_ISP_OB_SRC1_VALID_START_HORZ(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_OB_SRC1_VALID_START_VERT(v) (((v) << 16) & GENMASK(27, 16)) + +#define SUN6I_ISP_OB_SPRITE_REG 0x88 +#define SUN6I_ISP_OB_SPRITE_WIDTH(v) ((v) & GENMASK(12, 0)) +#define SUN6I_ISP_OB_SPRITE_HEIGHT(v) (((v) << 16) & GENMASK(28, 16)) + +#define SUN6I_ISP_OB_SPRITE_START_REG 0x8c +#define SUN6I_ISP_OB_SPRITE_START_HORZ(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_OB_SPRITE_START_VERT(v) (((v) << 16) & GENMASK(27, 16)) + +#define SUN6I_ISP_OB_CFG_REG 0x90 +#define SUN6I_ISP_OB_HORZ_POS_REG 0x94 +#define SUN6I_ISP_OB_VERT_PARA_REG 0x98 +#define SUN6I_ISP_OB_OFFSET_FIXED_REG 0x9c + +/* BDNF */ + +#define SUN6I_ISP_BDNF_CFG_REG 0xcc +#define SUN6I_ISP_BDNF_CFG_IN_DIS_MIN(v) ((v) & GENMASK(7, 0)) +#define SUN6I_ISP_BDNF_CFG_IN_DIS_MAX(v) (((v) << 16) & GENMASK(23, 16)) + +#define SUN6I_ISP_BDNF_COEF_RB_REG 0xd0 +#define SUN6I_ISP_BDNF_COEF_RB(i, v) (((v) << (4 * (i))) & \ + GENMASK(4 * (i) + 3, 4 * (i))) + +#define SUN6I_ISP_BDNF_COEF_G_REG 0xd4 +#define SUN6I_ISP_BDNF_COEF_G(i, v) (((v) << (4 * (i))) & \ + GENMASK(4 * (i) + 3, 4 * (i))) + +/* Bayer */ + +#define SUN6I_ISP_BAYER_OFFSET0_REG 0xe0 +#define SUN6I_ISP_BAYER_OFFSET0_R(v) ((v) & GENMASK(12, 0)) +#define SUN6I_ISP_BAYER_OFFSET0_GR(v) (((v) << 16) & GENMASK(28, 16)) + +#define SUN6I_ISP_BAYER_OFFSET1_REG 0xe4 +#define SUN6I_ISP_BAYER_OFFSET1_GB(v) ((v) & GENMASK(12, 0)) +#define SUN6I_ISP_BAYER_OFFSET1_B(v) (((v) << 16) & GENMASK(28, 16)) + +#define SUN6I_ISP_BAYER_GAIN0_REG 0xe8 +#define SUN6I_ISP_BAYER_GAIN0_R(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_BAYER_GAIN0_GR(v) (((v) << 16) & GENMASK(27, 16)) + +#define SUN6I_ISP_BAYER_GAIN1_REG 0xec +#define SUN6I_ISP_BAYER_GAIN1_GB(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_BAYER_GAIN1_B(v) (((v) << 16) & GENMASK(27, 16)) + +/* WB */ + +#define SUN6I_ISP_WB_GAIN0_REG 0x140 +#define SUN6I_ISP_WB_GAIN0_R(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_WB_GAIN0_GR(v) (((v) << 16) & GENMASK(27, 16)) + +#define SUN6I_ISP_WB_GAIN1_REG 0x144 +#define SUN6I_ISP_WB_GAIN1_GB(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_WB_GAIN1_B(v) (((v) << 16) & GENMASK(27, 16)) + +#define SUN6I_ISP_WB_CFG_REG 0x148 +#define SUN6I_ISP_WB_CFG_CLIP(v) ((v) & GENMASK(11, 0)) + +/* Global */ + +#define SUN6I_ISP_MCH_SIZE_CFG_REG 0x1e0 +#define SUN6I_ISP_MCH_SIZE_CFG_WIDTH(v) ((v) & GENMASK(12, 0)) +#define SUN6I_ISP_MCH_SIZE_CFG_HEIGHT(v) (((v) << 16) & GENMASK(28, 16)) + +#define SUN6I_ISP_MCH_SCALE_CFG_REG 0x1e4 +#define SUN6I_ISP_MCH_SCALE_CFG_X_RATIO(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_MCH_SCALE_CFG_Y_RATIO(v) (((v) << 16) & GENMASK(27, 16)) +#define SUN6I_ISP_MCH_SCALE_CFG_WEIGHT_SHIFT(v) (((v) << 28) & GENMASK(31, 28)) + +#define SUN6I_ISP_SCH_SIZE_CFG_REG 0x1e8 +#define SUN6I_ISP_SCH_SIZE_CFG_WIDTH(v) ((v) & GENMASK(12, 0)) +#define SUN6I_ISP_SCH_SIZE_CFG_HEIGHT(v) (((v) << 16) & GENMASK(28, 16)) + +#define SUN6I_ISP_SCH_SCALE_CFG_REG 0x1ec +#define SUN6I_ISP_SCH_SCALE_CFG_X_RATIO(v) ((v) & GENMASK(11, 0)) +#define SUN6I_ISP_SCH_SCALE_CFG_Y_RATIO(v) (((v) << 16) & GENMASK(27, 16)) +#define SUN6I_ISP_SCH_SCALE_CFG_WEIGHT_SHIFT(v) (((v) << 28) & GENMASK(31, 28)) + +#define SUN6I_ISP_MCH_CFG_REG 0x1f0 +#define SUN6I_ISP_MCH_CFG_EN BIT(0) +#define SUN6I_ISP_MCH_CFG_SCALE_EN BIT(1) +#define SUN6I_ISP_MCH_CFG_OUTPUT_FMT(v) (((v) << 2) & GENMASK(4, 2)) +#define SUN6I_ISP_MCH_CFG_MIRROR_EN BIT(5) +#define SUN6I_ISP_MCH_CFG_FLIP_EN BIT(6) +#define SUN6I_ISP_MCH_CFG_STRIDE_Y_DIV4(v) (((v) << 8) & GENMASK(18, 8)) +#define SUN6I_ISP_MCH_CFG_STRIDE_UV_DIV4(v) (((v) << 20) & GENMASK(30, 20)) + +#define SUN6I_ISP_OUTPUT_FMT_YUV420SP 0 +#define SUN6I_ISP_OUTPUT_FMT_YUV422SP 1 +#define SUN6I_ISP_OUTPUT_FMT_YVU420SP 2 +#define SUN6I_ISP_OUTPUT_FMT_YVU422SP 3 +#define SUN6I_ISP_OUTPUT_FMT_YUV420P 4 +#define SUN6I_ISP_OUTPUT_FMT_YUV422P 5 +#define SUN6I_ISP_OUTPUT_FMT_YVU420P 6 +#define SUN6I_ISP_OUTPUT_FMT_YVU422P 7 + +#define SUN6I_ISP_SCH_CFG_REG 0x1f4 + +#define SUN6I_ISP_MCH_Y_ADDR0_REG 0x1f8 +#define SUN6I_ISP_MCH_U_ADDR0_REG 0x1fc +#define SUN6I_ISP_MCH_V_ADDR0_REG 0x200 +#define SUN6I_ISP_MCH_Y_ADDR1_REG 0x204 +#define SUN6I_ISP_MCH_U_ADDR1_REG 0x208 +#define SUN6I_ISP_MCH_V_ADDR1_REG 0x20c +#define SUN6I_ISP_SCH_Y_ADDR0_REG 0x210 +#define SUN6I_ISP_SCH_U_ADDR0_REG 0x214 +#define SUN6I_ISP_SCH_V_ADDR0_REG 0x218 +#define SUN6I_ISP_SCH_Y_ADDR1_REG 0x21c +#define SUN6I_ISP_SCH_U_ADDR1_REG 0x220 +#define SUN6I_ISP_SCH_V_ADDR1_REG 0x224 + +#endif diff --git a/drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h b/drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h new file mode 100644 index 000000000000..19c24c5fd322 --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: ((GPL-2.0+ WITH Linux-syscall-note) OR MIT) */ +/* + * Allwinner A31 ISP Configuration + */ + +#ifndef _UAPI_SUN6I_ISP_CONFIG_H +#define _UAPI_SUN6I_ISP_CONFIG_H + +#include <linux/types.h> + +#define V4L2_META_FMT_SUN6I_ISP_PARAMS v4l2_fourcc('S', '6', 'I', 'P') /* Allwinner A31 ISP Parameters */ + +#define SUN6I_ISP_MODULE_BAYER (1U << 0) +#define SUN6I_ISP_MODULE_BDNF (1U << 1) + +struct sun6i_isp_params_config_bayer { + __u16 offset_r; + __u16 offset_gr; + __u16 offset_gb; + __u16 offset_b; + + __u16 gain_r; + __u16 gain_gr; + __u16 gain_gb; + __u16 gain_b; +}; + +struct sun6i_isp_params_config_bdnf { + __u8 in_dis_min; + __u8 in_dis_max; + + __u8 coefficients_g[7]; + __u8 coefficients_rb[5]; +}; + +struct sun6i_isp_params_config { + __u32 modules_used; + + struct sun6i_isp_params_config_bayer bayer; + struct sun6i_isp_params_config_bdnf bdnf; +}; + +#endif /* _UAPI_SUN6I_ISP_CONFIG_H */ diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c index b26e44adb2be..426e653bd55d 100644 --- a/drivers/staging/media/tegra-video/csi.c +++ b/drivers/staging/media/tegra-video/csi.c @@ -433,7 +433,7 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi, for (i = 0; i < chan->numgangports; i++) chan->csi_port_nums[i] = port_num + i * CSI_PORTS_PER_BRICK; - chan->of_node = node; + chan->of_node = of_node_get(node); chan->numpads = num_pads; if (num_pads & 0x2) { chan->pads[0].flags = MEDIA_PAD_FL_SINK; @@ -448,6 +448,7 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi, chan->mipi = tegra_mipi_request(csi->dev, node); if (IS_ERR(chan->mipi)) { ret = PTR_ERR(chan->mipi); + chan->mipi = NULL; dev_err(csi->dev, "failed to get mipi device: %d\n", ret); } @@ -640,6 +641,7 @@ static void tegra_csi_channels_cleanup(struct tegra_csi *csi) media_entity_cleanup(&subdev->entity); } + of_node_put(chan->of_node); list_del(&chan->list); kfree(chan); } diff --git a/drivers/staging/media/tegra-video/csi.h b/drivers/staging/media/tegra-video/csi.h index 4ee05a1785cf..6960ea2e3d36 100644 --- a/drivers/staging/media/tegra-video/csi.h +++ b/drivers/staging/media/tegra-video/csi.h @@ -56,7 +56,7 @@ struct tegra_csi; * @framerate: active framerate for TPG * @h_blank: horizontal blanking for TPG active format * @v_blank: vertical blanking for TPG active format - * @mipi: mipi device for corresponding csi channel pads + * @mipi: mipi device for corresponding csi channel pads, or NULL if not applicable (TPG, error) * @pixel_rate: active pixel rate from the sensor on this channel */ struct tegra_csi_channel { diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c index 9d46a36cc014..11dd142c98c5 100644 --- a/drivers/staging/media/tegra-video/vi.c +++ b/drivers/staging/media/tegra-video/vi.c @@ -1811,7 +1811,7 @@ static int tegra_vi_graph_parse_one(struct tegra_vi_channel *chan, } /* skip entities that are already processed */ - if (remote == dev_fwnode(vi->dev) || + if (device_match_fwnode(vi->dev, remote) || tegra_vi_graph_find_entity(chan, remote)) { fwnode_handle_put(remote); continue; diff --git a/include/dt-bindings/media/video-interfaces.h b/include/dt-bindings/media/video-interfaces.h new file mode 100644 index 000000000000..68ac4e05e37f --- /dev/null +++ b/include/dt-bindings/media/video-interfaces.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2022 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __DT_BINDINGS_MEDIA_VIDEO_INTERFACES_H__ +#define __DT_BINDINGS_MEDIA_VIDEO_INTERFACES_H__ + +#define MEDIA_BUS_TYPE_CSI2_CPHY 1 +#define MEDIA_BUS_TYPE_CSI1 2 +#define MEDIA_BUS_TYPE_CCP2 3 +#define MEDIA_BUS_TYPE_CSI2_DPHY 4 +#define MEDIA_BUS_TYPE_PARALLEL 5 +#define MEDIA_BUS_TYPE_BT656 6 + +#endif /* __DT_BINDINGS_MEDIA_VIDEO_INTERFACES_H__ */ diff --git a/include/linux/phy/phy-mipi-dphy.h b/include/linux/phy/phy-mipi-dphy.h index a877ffee845d..1ac128d78dfe 100644 --- a/include/linux/phy/phy-mipi-dphy.h +++ b/include/linux/phy/phy-mipi-dphy.h @@ -279,6 +279,9 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, unsigned int bpp, unsigned int lanes, struct phy_configure_opts_mipi_dphy *cfg); +int phy_mipi_dphy_get_default_config_for_hsclk(unsigned long long hs_clk_rate, + unsigned int lanes, + struct phy_configure_opts_mipi_dphy *cfg); int phy_mipi_dphy_config_validate(struct phy_configure_opts_mipi_dphy *cfg); #endif /* __PHY_MIPI_DPHY_H_ */ diff --git a/include/media/davinci/vpbe.h b/include/media/davinci/vpbe.h index e74a93475d21..646c4b48b29d 100644 --- a/include/media/davinci/vpbe.h +++ b/include/media/davinci/vpbe.h @@ -28,7 +28,7 @@ struct vpbe_output { */ char *subdev_name; /* - * defualt_mode identifies the default timings set at the venc or + * default_mode identifies the default timings set at the venc or * external encoder. */ char *default_mode; diff --git a/include/media/dvb_ringbuffer.h b/include/media/dvb_ringbuffer.h index 8ed6bcc3a56e..029c8b615e49 100644 --- a/include/media/dvb_ringbuffer.h +++ b/include/media/dvb_ringbuffer.h @@ -214,7 +214,7 @@ extern ssize_t dvb_ringbuffer_write_user(struct dvb_ringbuffer *rbuf, * @buf: Buffer to write. * @len: Length of buffer (currently limited to 65535 bytes max). * - * Return: Number of bytes written, or -EFAULT, -ENOMEM, -EVINAL. + * Return: Number of bytes written, or -EFAULT, -ENOMEM, -EINVAL. */ extern ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8 *buf, size_t len); diff --git a/include/media/dvbdev.h b/include/media/dvbdev.h index 2f6b0861322a..29d25c8a6f13 100644 --- a/include/media/dvbdev.h +++ b/include/media/dvbdev.h @@ -87,7 +87,11 @@ struct dvb_frontend; * @device: pointer to struct device * @module: pointer to struct module * @mfe_shared: indicates mutually exclusive frontends. - * Use of this flag is currently deprecated. + * 1 = legacy exclusion behavior: blocking any open() call + * 2 = enhanced exclusion behavior, emulating the standard + * behavior of busy frontends: allowing read-only sharing + * and otherwise returning immediately with -EBUSY when any + * of the frontends is already opened with write access. * @mfe_dvbdev: Frontend device in use, in the case of MFE * @mfe_lock: Lock to prevent using the other frontends when MFE is * used. @@ -126,6 +130,7 @@ struct dvb_adapter { * struct dvb_device - represents a DVB device node * * @list_head: List head with all DVB devices + * @ref: reference counter * @fops: pointer to struct file_operations * @adapter: pointer to the adapter that holds this device node * @type: type of the device, as defined by &enum dvb_device_type. @@ -156,6 +161,7 @@ struct dvb_adapter { */ struct dvb_device { struct list_head list_head; + struct kref ref; const struct file_operations *fops; struct dvb_adapter *adapter; enum dvb_device_type type; @@ -188,6 +194,20 @@ struct dvb_device { }; /** + * dvb_device_get - Increase dvb_device reference + * + * @dvbdev: pointer to struct dvb_device + */ +struct dvb_device *dvb_device_get(struct dvb_device *dvbdev); + +/** + * dvb_device_put - Decrease dvb_device reference + * + * @dvbdev: pointer to struct dvb_device + */ +void dvb_device_put(struct dvb_device *dvbdev); + +/** * dvb_register_adapter - Registers a new DVB adapter * * @adap: pointer to struct dvb_adapter @@ -231,29 +251,17 @@ int dvb_register_device(struct dvb_adapter *adap, /** * dvb_remove_device - Remove a registered DVB device * - * This does not free memory. To do that, call dvb_free_device(). + * This does not free memory. dvb_free_device() will do that when + * reference counter is empty * * @dvbdev: pointer to struct dvb_device */ void dvb_remove_device(struct dvb_device *dvbdev); -/** - * dvb_free_device - Free memory occupied by a DVB device. - * - * Call dvb_unregister_device() before calling this function. - * - * @dvbdev: pointer to struct dvb_device - */ -void dvb_free_device(struct dvb_device *dvbdev); /** * dvb_unregister_device - Unregisters a DVB device * - * This is a combination of dvb_remove_device() and dvb_free_device(). - * Using this function is usually a mistake, and is often an indicator - * for a use-after-free bug (when a userspace process keeps a file - * handle to a detached device). - * * @dvbdev: pointer to struct dvb_device */ void dvb_unregister_device(struct dvb_device *dvbdev); diff --git a/include/media/frame_vector.h b/include/media/frame_vector.h index bfed1710dc24..541c71a2c7be 100644 --- a/include/media/frame_vector.h +++ b/include/media/frame_vector.h @@ -16,7 +16,7 @@ struct frame_vector { struct frame_vector *frame_vector_create(unsigned int nr_frames); void frame_vector_destroy(struct frame_vector *vec); int get_vaddr_frames(unsigned long start, unsigned int nr_pfns, - struct frame_vector *vec); + bool write, struct frame_vector *vec); void put_vaddr_frames(struct frame_vector *vec); int frame_vector_to_pages(struct frame_vector *vec); void frame_vector_to_pfns(struct frame_vector *vec); diff --git a/include/media/i2c/ov9650.h b/include/media/i2c/ov9650.h deleted file mode 100644 index 3ec7e06955b4..000000000000 --- a/include/media/i2c/ov9650.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * OV9650/OV9652 camera sensors driver - * - * Copyright (C) 2013 Sylwester Nawrocki <sylvester.nawrocki@gmail.com> - */ -#ifndef OV9650_H_ -#define OV9650_H_ - -/** - * struct ov9650_platform_data - ov9650 driver platform data - * @mclk_frequency: the sensor's master clock frequency in Hz - * @gpio_pwdn: number of a GPIO connected to OV965X PWDN pin - * @gpio_reset: number of a GPIO connected to OV965X RESET pin - * - * If any of @gpio_pwdn or @gpio_reset are unused then they should be - * set to a negative value. @mclk_frequency must always be specified. - */ -struct ov9650_platform_data { - unsigned long mclk_frequency; - int gpio_pwdn; - int gpio_reset; -}; -#endif /* OV9650_H_ */ diff --git a/include/media/i2c/s5c73m3.h b/include/media/i2c/s5c73m3.h index a51f1025ba1c..df0769d64523 100644 --- a/include/media/i2c/s5c73m3.h +++ b/include/media/i2c/s5c73m3.h @@ -21,20 +21,8 @@ #include <media/v4l2-mediabus.h> /** - * struct s5c73m3_gpio - data structure describing a GPIO - * @gpio: GPIO number - * @level: indicates active state of the @gpio - */ -struct s5c73m3_gpio { - int gpio; - int level; -}; - -/** * struct s5c73m3_platform_data - s5c73m3 driver platform data * @mclk_frequency: sensor's master clock frequency in Hz - * @gpio_reset: GPIO driving RESET pin - * @gpio_stby: GPIO driving STBY pin * @bus_type: bus type * @nlanes: maximum number of MIPI-CSI lanes used * @horiz_flip: default horizontal image flip value, non zero to enable @@ -44,9 +32,6 @@ struct s5c73m3_gpio { struct s5c73m3_platform_data { unsigned long mclk_frequency; - struct s5c73m3_gpio gpio_reset; - struct s5c73m3_gpio gpio_stby; - enum v4l2_mbus_type bus_type; u8 nlanes; u8 horiz_flip; diff --git a/include/media/i2c/s5k4ecgx.h b/include/media/i2c/s5k4ecgx.h deleted file mode 100644 index 92202eb35249..000000000000 --- a/include/media/i2c/s5k4ecgx.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * S5K4ECGX image sensor header file - * - * Copyright (C) 2012, Linaro - * Copyright (C) 2012, Samsung Electronics Co., Ltd. - */ - -#ifndef S5K4ECGX_H -#define S5K4ECGX_H - -/** - * struct s5k4ecgx_gpio - data structure describing a GPIO - * @gpio: GPIO number - * @level: indicates active state of the @gpio - */ -struct s5k4ecgx_gpio { - int gpio; - int level; -}; - -/** - * struct s5k4ecgx_platform_data - s5k4ecgx driver platform data - * @gpio_reset: GPIO driving RESET pin - * @gpio_stby: GPIO driving STBY pin - */ - -struct s5k4ecgx_platform_data { - struct s5k4ecgx_gpio gpio_reset; - struct s5k4ecgx_gpio gpio_stby; -}; - -#endif /* S5K4ECGX_H */ diff --git a/include/media/i2c/ths7303.h b/include/media/i2c/ths7303.h index 95492d12786d..fee2818c558d 100644 --- a/include/media/i2c/ths7303.h +++ b/include/media/i2c/ths7303.h @@ -10,8 +10,8 @@ * Martin Bugge <marbugge@cisco.com> */ -#ifndef THS7353_H -#define THS7353_H +#ifndef THS7303_H +#define THS7303_H /** * struct ths7303_platform_data - Platform dependent data diff --git a/include/media/media-entity.h b/include/media/media-entity.h index 28c9de8a1f34..85ed08ddee9d 100644 --- a/include/media/media-entity.h +++ b/include/media/media-entity.h @@ -237,7 +237,7 @@ struct media_pad { * @link_validate: Return whether a link is valid from the entity point of * view. The media_pipeline_start() function * validates all links by calling this operation. Optional. - * @has_pad_interdep: Return whether a two pads inside the entity are + * @has_pad_interdep: Return whether two pads of the entity are * interdependent. If two pads are interdependent they are * part of the same pipeline and enabling one of the pads * means that the other pad will become "locked" and @@ -1144,7 +1144,7 @@ __must_check int __media_pipeline_start(struct media_pad *pad, * media_pipeline_stop - Mark a pipeline as not streaming * @pad: Starting pad * - * Mark all pads connected to a given pads through enabled links, either + * Mark all pads connected to a given pad through enabled links, either * directly or indirectly, as not streaming. The media_pad pipe field is * reset to %NULL. * diff --git a/include/media/v4l2-mediabus.h b/include/media/v4l2-mediabus.h index f67a74daf799..5bce6e423e94 100644 --- a/include/media/v4l2-mediabus.h +++ b/include/media/v4l2-mediabus.h @@ -54,17 +54,18 @@ #define V4L2_MBUS_VSYNC_ACTIVE_LOW BIT(5) #define V4L2_MBUS_PCLK_SAMPLE_RISING BIT(6) #define V4L2_MBUS_PCLK_SAMPLE_FALLING BIT(7) -#define V4L2_MBUS_DATA_ACTIVE_HIGH BIT(8) -#define V4L2_MBUS_DATA_ACTIVE_LOW BIT(9) +#define V4L2_MBUS_PCLK_SAMPLE_DUALEDGE BIT(8) +#define V4L2_MBUS_DATA_ACTIVE_HIGH BIT(9) +#define V4L2_MBUS_DATA_ACTIVE_LOW BIT(10) /* FIELD = 0/1 - Field1 (odd)/Field2 (even) */ -#define V4L2_MBUS_FIELD_EVEN_HIGH BIT(10) +#define V4L2_MBUS_FIELD_EVEN_HIGH BIT(11) /* FIELD = 1/0 - Field1 (odd)/Field2 (even) */ -#define V4L2_MBUS_FIELD_EVEN_LOW BIT(11) +#define V4L2_MBUS_FIELD_EVEN_LOW BIT(12) /* Active state of Sync-on-green (SoG) signal, 0/1 for LOW/HIGH respectively. */ -#define V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH BIT(12) -#define V4L2_MBUS_VIDEO_SOG_ACTIVE_LOW BIT(13) -#define V4L2_MBUS_DATA_ENABLE_HIGH BIT(14) -#define V4L2_MBUS_DATA_ENABLE_LOW BIT(15) +#define V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH BIT(13) +#define V4L2_MBUS_VIDEO_SOG_ACTIVE_LOW BIT(14) +#define V4L2_MBUS_DATA_ENABLE_HIGH BIT(15) +#define V4L2_MBUS_DATA_ENABLE_LOW BIT(16) /* Serial flags */ /* Clock non-continuous mode support. */ diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 2f80c9c818ed..b15fa9930f30 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -176,7 +176,10 @@ struct v4l2_subdev_io_pin_config { * @s_register: callback for VIDIOC_DBG_S_REGISTER() ioctl handler code. * * @s_power: puts subdevice in power saving mode (on == 0) or normal operation - * mode (on == 1). + * mode (on == 1). DEPRECATED. See + * Documentation/driver-api/media/camera-sensor.rst . pre_streamon and + * post_streamoff callbacks can be used for e.g. setting the bus to LP-11 + * mode before s_stream is called. * * @interrupt_service_routine: Called by the bridge chip's interrupt service * handler, when an interrupt status has be raised due to this subdev, @@ -437,8 +440,10 @@ enum v4l2_subdev_pre_streamon_flags { * @g_input_status: get input status. Same as the status field in the * &struct v4l2_input * - * @s_stream: used to notify the driver that a video stream will start or has - * stopped. + * @s_stream: start (enabled == 1) or stop (enabled == 0) streaming on the + * sub-device. Failure on stop will remove any resources acquired in + * streaming start, while the error code is still returned by the driver. + * Also see call_s_stream wrapper in v4l2-subdev.c. * * @g_pixelaspect: callback to return the pixelaspect ratio. * diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h index 3253bd2f6fee..4b6a9d2ea372 100644 --- a/include/media/videobuf2-core.h +++ b/include/media/videobuf2-core.h @@ -386,6 +386,12 @@ struct vb2_buffer { * the buffer contents will be ignored anyway. * @buf_cleanup: called once before the buffer is freed; drivers may * perform any additional cleanup; optional. + * @prepare_streaming: called once to prepare for 'streaming' state; this is + * where validation can be done to verify everything is + * okay and streaming resources can be claimed. It is + * called when the VIDIOC_STREAMON ioctl is called. The + * actual streaming starts when @start_streaming is called. + * Optional. * @start_streaming: called once to enter 'streaming' state; the driver may * receive buffers with @buf_queue callback * before @start_streaming is called; the driver gets the @@ -405,6 +411,10 @@ struct vb2_buffer { * callback by calling vb2_buffer_done() with either * %VB2_BUF_STATE_DONE or %VB2_BUF_STATE_ERROR; may use * vb2_wait_for_all_buffers() function + * @unprepare_streaming:called as counterpart to @prepare_streaming; any claimed + * streaming resources can be released here. It is + * called when the VIDIOC_STREAMOFF ioctls is called or + * when the streaming filehandle is closed. Optional. * @buf_queue: passes buffer vb to the driver; driver may start * hardware operation on this buffer; driver should give * the buffer back by calling vb2_buffer_done() function; @@ -432,8 +442,10 @@ struct vb2_ops { void (*buf_finish)(struct vb2_buffer *vb); void (*buf_cleanup)(struct vb2_buffer *vb); + int (*prepare_streaming)(struct vb2_queue *q); int (*start_streaming)(struct vb2_queue *q, unsigned int count); void (*stop_streaming)(struct vb2_queue *q); + void (*unprepare_streaming)(struct vb2_queue *q); void (*buf_queue)(struct vb2_buffer *vb); @@ -641,8 +653,10 @@ struct vb2_queue { u32 cnt_queue_setup; u32 cnt_wait_prepare; u32 cnt_wait_finish; + u32 cnt_prepare_streaming; u32 cnt_start_streaming; u32 cnt_stop_streaming; + u32 cnt_unprepare_streaming; #endif }; diff --git a/include/media/videobuf2-memops.h b/include/media/videobuf2-memops.h index cd4a46331531..4b5b84f93538 100644 --- a/include/media/videobuf2-memops.h +++ b/include/media/videobuf2-memops.h @@ -34,7 +34,8 @@ struct vb2_vmarea_handler { extern const struct vm_operations_struct vb2_common_vm_ops; struct frame_vector *vb2_create_framevec(unsigned long start, - unsigned long length); + unsigned long length, + bool write); void vb2_destroy_framevec(struct frame_vector *vec); #endif diff --git a/include/uapi/linux/aspeed-video.h b/include/uapi/linux/aspeed-video.h new file mode 100644 index 000000000000..6586a65548c4 --- /dev/null +++ b/include/uapi/linux/aspeed-video.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Copyright (C) 2021 ASPEED Technology Inc. + */ + +#ifndef _UAPI_LINUX_ASPEED_VIDEO_H +#define _UAPI_LINUX_ASPEED_VIDEO_H + +#include <linux/v4l2-controls.h> + +#define V4L2_CID_ASPEED_HQ_MODE (V4L2_CID_USER_ASPEED_BASE + 1) +#define V4L2_CID_ASPEED_HQ_JPEG_QUALITY (V4L2_CID_USER_ASPEED_BASE + 2) + +#endif /* _UAPI_LINUX_ASPEED_VIDEO_H */ diff --git a/include/uapi/linux/dvb/audio.h b/include/uapi/linux/dvb/audio.h index 2f869da69171..77fb866890b4 100644 --- a/include/uapi/linux/dvb/audio.h +++ b/include/uapi/linux/dvb/audio.h @@ -7,21 +7,6 @@ * Copyright (C) 2000 Ralph Metzler <ralph@convergence.de> * & Marcus Metzler <marcus@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Lesser Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _DVBAUDIO_H_ diff --git a/include/uapi/linux/dvb/ca.h b/include/uapi/linux/dvb/ca.h index dffa59e95ebb..4244b187cc4d 100644 --- a/include/uapi/linux/dvb/ca.h +++ b/include/uapi/linux/dvb/ca.h @@ -5,21 +5,6 @@ * Copyright (C) 2000 Ralph Metzler <ralph@convergence.de> * & Marcus Metzler <marcus@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Lesser Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _DVBCA_H_ diff --git a/include/uapi/linux/dvb/dmx.h b/include/uapi/linux/dvb/dmx.h index b4112f0b6dd3..7b16375f94e2 100644 --- a/include/uapi/linux/dvb/dmx.h +++ b/include/uapi/linux/dvb/dmx.h @@ -5,21 +5,6 @@ * Copyright (C) 2000 Marcus Metzler <marcus@convergence.de> * & Ralph Metzler <ralph@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _UAPI_DVBDMX_H_ diff --git a/include/uapi/linux/dvb/frontend.h b/include/uapi/linux/dvb/frontend.h index 4f9b4551c534..326f6a53f1f2 100644 --- a/include/uapi/linux/dvb/frontend.h +++ b/include/uapi/linux/dvb/frontend.h @@ -7,21 +7,6 @@ * Holger Waechtler <holger@convergence.de> * Andre Draszik <ad@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _DVBFRONTEND_H_ @@ -282,7 +267,6 @@ enum fe_spectral_inversion { /** * enum fe_code_rate - Type of Forward Error Correction (FEC) * - * * @FEC_NONE: No Forward Error Correction Code * @FEC_1_2: Forward Error Correction Code 1/2 * @FEC_2_3: Forward Error Correction Code 2/3 @@ -296,6 +280,22 @@ enum fe_spectral_inversion { * @FEC_3_5: Forward Error Correction Code 3/5 * @FEC_9_10: Forward Error Correction Code 9/10 * @FEC_2_5: Forward Error Correction Code 2/5 + * @FEC_1_3: Forward Error Correction Code 1/3 + * @FEC_1_4: Forward Error Correction Code 1/4 + * @FEC_5_9: Forward Error Correction Code 5/9 + * @FEC_7_9: Forward Error Correction Code 7/9 + * @FEC_8_15: Forward Error Correction Code 8/15 + * @FEC_11_15: Forward Error Correction Code 11/15 + * @FEC_13_18: Forward Error Correction Code 13/18 + * @FEC_9_20: Forward Error Correction Code 9/20 + * @FEC_11_20: Forward Error Correction Code 11/20 + * @FEC_23_36: Forward Error Correction Code 23/36 + * @FEC_25_36: Forward Error Correction Code 25/36 + * @FEC_13_45: Forward Error Correction Code 13/45 + * @FEC_26_45: Forward Error Correction Code 26/45 + * @FEC_28_45: Forward Error Correction Code 28/45 + * @FEC_32_45: Forward Error Correction Code 32/45 + * @FEC_77_90: Forward Error Correction Code 77/90 * * Please note that not all FEC types are supported by a given standard. */ @@ -313,6 +313,22 @@ enum fe_code_rate { FEC_3_5, FEC_9_10, FEC_2_5, + FEC_1_3, + FEC_1_4, + FEC_5_9, + FEC_7_9, + FEC_8_15, + FEC_11_15, + FEC_13_18, + FEC_9_20, + FEC_11_20, + FEC_23_36, + FEC_25_36, + FEC_13_45, + FEC_26_45, + FEC_28_45, + FEC_32_45, + FEC_77_90, }; /** @@ -331,6 +347,13 @@ enum fe_code_rate { * @APSK_32: 32-APSK modulation * @DQPSK: DQPSK modulation * @QAM_4_NR: 4-QAM-NR modulation + * @QAM_1024: 1024-QAM modulation + * @QAM_4096: 4096-QAM modulation + * @APSK_8_L: 8APSK-L modulation + * @APSK_16_L: 16APSK-L modulation + * @APSK_32_L: 32APSK-L modulation + * @APSK_64: 64APSK modulation + * @APSK_64_L: 64APSK-L modulation * * Please note that not all modulations are supported by a given standard. * @@ -350,6 +373,13 @@ enum fe_modulation { APSK_32, DQPSK, QAM_4_NR, + QAM_1024, + QAM_4096, + APSK_8_L, + APSK_16_L, + APSK_32_L, + APSK_64, + APSK_64_L, }; /** @@ -404,6 +434,7 @@ enum fe_transmit_mode { * @GUARD_INTERVAL_PN420: PN length 420 (1/4) * @GUARD_INTERVAL_PN595: PN length 595 (1/6) * @GUARD_INTERVAL_PN945: PN length 945 (1/9) + * @GUARD_INTERVAL_1_64: Guard interval 1/64 * * Please note that not all guard intervals are supported by a given standard. */ @@ -419,6 +450,7 @@ enum fe_guard_interval { GUARD_INTERVAL_PN420, GUARD_INTERVAL_PN595, GUARD_INTERVAL_PN945, + GUARD_INTERVAL_1_64, }; /** @@ -571,6 +603,9 @@ enum fe_pilot { * @ROLLOFF_20: Roloff factor: α=20% * @ROLLOFF_25: Roloff factor: α=25% * @ROLLOFF_AUTO: Auto-detect the roloff factor. + * @ROLLOFF_15: Rolloff factor: α=15% + * @ROLLOFF_10: Rolloff factor: α=10% + * @ROLLOFF_5: Rolloff factor: α=5% * * .. note: * @@ -581,6 +616,9 @@ enum fe_rolloff { ROLLOFF_20, ROLLOFF_25, ROLLOFF_AUTO, + ROLLOFF_15, + ROLLOFF_10, + ROLLOFF_5, }; /** @@ -594,6 +632,8 @@ enum fe_rolloff { * Cable TV: DVB-C following ITU-T J.83 Annex B spec (ClearQAM) * @SYS_DVBC_ANNEX_C: * Cable TV: DVB-C following ITU-T J.83 Annex C spec + * @SYS_DVBC2: + * Cable TV: DVB-C2 * @SYS_ISDBC: * Cable TV: ISDB-C (no drivers yet) * @SYS_DVBT: @@ -611,7 +651,7 @@ enum fe_rolloff { * @SYS_DVBS: * Satellite TV: DVB-S * @SYS_DVBS2: - * Satellite TV: DVB-S2 + * Satellite TV: DVB-S2 and DVB-S2X * @SYS_TURBO: * Satellite TV: DVB-S Turbo * @SYS_ISDBS: @@ -645,6 +685,7 @@ enum fe_delivery_system { SYS_DVBT2, SYS_TURBO, SYS_DVBC_ANNEX_C, + SYS_DVBC2, }; /* backward compatibility definitions for delivery systems */ @@ -720,7 +761,7 @@ enum atscmh_rs_frame_mode { }; /** - * enum atscmh_rs_code_mode + * enum atscmh_rs_code_mode - ATSC-M/H Reed Solomon modes * @ATSCMH_RSCODE_211_187: Reed Solomon code (211,187). * @ATSCMH_RSCODE_223_187: Reed Solomon code (223,187). * @ATSCMH_RSCODE_235_187: Reed Solomon code (235,187). diff --git a/include/uapi/linux/dvb/net.h b/include/uapi/linux/dvb/net.h index 0c550ef93f2c..7cbb47ac38ef 100644 --- a/include/uapi/linux/dvb/net.h +++ b/include/uapi/linux/dvb/net.h @@ -5,21 +5,6 @@ * Copyright (C) 2000 Marcus Metzler <marcus@convergence.de> * & Ralph Metzler <ralph@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _DVBNET_H_ diff --git a/include/uapi/linux/dvb/osd.h b/include/uapi/linux/dvb/osd.h index 858997c74043..6003b108ba45 100644 --- a/include/uapi/linux/dvb/osd.h +++ b/include/uapi/linux/dvb/osd.h @@ -7,21 +7,6 @@ * Copyright (C) 2001 Ralph Metzler <ralph@convergence.de> * & Marcus Metzler <marcus@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Lesser Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _DVBOSD_H_ diff --git a/include/uapi/linux/dvb/version.h b/include/uapi/linux/dvb/version.h index 2c5cffe6d2a0..1a8cd038aa0b 100644 --- a/include/uapi/linux/dvb/version.h +++ b/include/uapi/linux/dvb/version.h @@ -4,21 +4,6 @@ * * Copyright (C) 2000 Holger Waechtler <holger@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _DVBVERSION_H_ diff --git a/include/uapi/linux/dvb/video.h b/include/uapi/linux/dvb/video.h index 179f1ec60af6..9910b73737e0 100644 --- a/include/uapi/linux/dvb/video.h +++ b/include/uapi/linux/dvb/video.h @@ -7,21 +7,6 @@ * Copyright (C) 2000 Marcus Metzler <marcus@convergence.de> * & Ralph Metzler <ralph@convergence.de> * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * */ #ifndef _UAPI_DVBVIDEO_H_ diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h index ec3323dbb927..ca9a24c874da 100644 --- a/include/uapi/linux/media-bus-format.h +++ b/include/uapi/linux/media-bus-format.h @@ -69,7 +69,7 @@ #define MEDIA_BUS_FMT_RGB121212_1X36 0x1019 #define MEDIA_BUS_FMT_RGB161616_1X48 0x101a -/* YUV (including grey) - next is 0x202e */ +/* YUV (including grey) - next is 0x202f */ #define MEDIA_BUS_FMT_Y8_1X8 0x2001 #define MEDIA_BUS_FMT_UV8_1X8 0x2015 #define MEDIA_BUS_FMT_UYVY8_1_5X8 0x2002 @@ -92,6 +92,7 @@ #define MEDIA_BUS_FMT_YUYV12_2X12 0x201e #define MEDIA_BUS_FMT_YVYU12_2X12 0x201f #define MEDIA_BUS_FMT_Y14_1X14 0x202d +#define MEDIA_BUS_FMT_Y16_1X16 0x202e #define MEDIA_BUS_FMT_UYVY8_1X16 0x200f #define MEDIA_BUS_FMT_VYUY8_1X16 0x2010 #define MEDIA_BUS_FMT_YUYV8_1X16 0x2011 diff --git a/include/uapi/linux/v4l2-common.h b/include/uapi/linux/v4l2-common.h index 7d21c1634b4d..5a23a15d78ac 100644 --- a/include/uapi/linux/v4l2-common.h +++ b/include/uapi/linux/v4l2-common.h @@ -10,45 +10,6 @@ * * Copyright (C) 2012 Nokia Corporation * Contact: Sakari Ailus <sakari.ailus@iki.fi> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * Alternatively you can redistribute this file under the terms of the - * BSD license as stated below: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * 3. The names of its contributors may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * */ #ifndef __V4L2_COMMON__ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index b5e7d082b8ad..b73a8ba7df6c 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -4,44 +4,6 @@ * * Copyright (C) 1999-2012 the contributors * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * Alternatively you can redistribute this file under the terms of the - * BSD license as stated below: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * 3. The names of its contributors may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * * The contents of this header was split off from videodev2.h. All control * definitions should be added to this header, which is included by * videodev2.h. @@ -231,6 +193,12 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_DW100_BASE (V4L2_CID_USER_BASE + 0x1190) +/* + * The base for Aspeed driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_ASPEED_BASE (V4L2_CID_USER_BASE + 0x11a0) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ @@ -1019,6 +987,8 @@ enum v4l2_auto_focus_range { #define V4L2_CID_CAMERA_SENSOR_ROTATION (V4L2_CID_CAMERA_CLASS_BASE+35) +#define V4L2_CID_HDR_SENSOR_MODE (V4L2_CID_CAMERA_CLASS_BASE+36) + /* FM Modulator class control IDs */ #define V4L2_CID_FM_TX_CLASS_BASE (V4L2_CTRL_CLASS_FM_TX | 0x900) diff --git a/include/uapi/linux/v4l2-dv-timings.h b/include/uapi/linux/v4l2-dv-timings.h index b52b67c62562..ef0128c7369c 100644 --- a/include/uapi/linux/v4l2-dv-timings.h +++ b/include/uapi/linux/v4l2-dv-timings.h @@ -3,15 +3,6 @@ * V4L2 DV timings header. * * Copyright (C) 2012-2016 Hans Verkuil <hans.verkuil@cisco.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. */ #ifndef _V4L2_DV_TIMINGS_H diff --git a/include/uapi/linux/v4l2-mediabus.h b/include/uapi/linux/v4l2-mediabus.h index 903e67b16711..6b07b73473b5 100644 --- a/include/uapi/linux/v4l2-mediabus.h +++ b/include/uapi/linux/v4l2-mediabus.h @@ -3,10 +3,6 @@ * Media Bus API header * * Copyright (C) 2009, Guennadi Liakhovetski <g.liakhovetski@gmx.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef __LINUX_V4L2_MEDIABUS_H diff --git a/include/uapi/linux/v4l2-subdev.h b/include/uapi/linux/v4l2-subdev.h index 658106f5b5dc..ecce4c79f5c5 100644 --- a/include/uapi/linux/v4l2-subdev.h +++ b/include/uapi/linux/v4l2-subdev.h @@ -6,19 +6,6 @@ * * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> * Sakari Ailus <sakari.ailus@iki.fi> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __LINUX_V4L2_SUBDEV_H diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 29da1f4b4578..1befd181a4cc 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -654,6 +654,8 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_NV12_16L16 v4l2_fourcc('H', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 16x16 tiles */ #define V4L2_PIX_FMT_NV12_32L32 v4l2_fourcc('S', 'T', '1', '2') /* 12 Y/CbCr 4:2:0 32x32 tiles */ #define V4L2_PIX_FMT_P010_4L4 v4l2_fourcc('T', '0', '1', '0') /* 12 Y/CbCr 4:2:0 10-bit 4x4 macroblocks */ +#define V4L2_PIX_FMT_NV12_8L128 v4l2_fourcc('A', 'T', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */ +#define V4L2_PIX_FMT_NV12_10BE_8L128 v4l2_fourcc_be('A', 'X', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */ /* Tiled YUV formats, non contiguous planes */ #define V4L2_PIX_FMT_NV12MT v4l2_fourcc('T', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 64x32 tiles */ @@ -775,6 +777,7 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_HI240 v4l2_fourcc('H', 'I', '2', '4') /* BTTV 8-bit dithered RGB */ #define V4L2_PIX_FMT_QC08C v4l2_fourcc('Q', '0', '8', 'C') /* Qualcomm 8-bit compressed */ #define V4L2_PIX_FMT_QC10C v4l2_fourcc('Q', '1', '0', 'C') /* Qualcomm 10-bit compressed */ +#define V4L2_PIX_FMT_AJPG v4l2_fourcc('A', 'J', 'P', 'G') /* Aspeed JPEG */ /* 10bit raw packed, 32 bytes for every 25 pixels, last LSB 6 bits unused */ #define V4L2_PIX_FMT_IPU3_SBGGR10 v4l2_fourcc('i', 'p', '3', 'b') /* IPU3 packed 10-bit BGGR bayer */ @@ -1780,6 +1783,8 @@ struct v4l2_ext_control { __u8 __user *p_u8; __u16 __user *p_u16; __u32 __user *p_u32; + __u32 __user *p_s32; + __u32 __user *p_s64; struct v4l2_area __user *p_area; struct v4l2_ctrl_h264_sps __user *p_h264_sps; struct v4l2_ctrl_h264_pps *p_h264_pps; |