* [PATCH v6 0/3] add IPU3 CIO2 CSI2 driver
@ 2017-10-26 2:24 Yong Zhi
2017-10-26 2:24 ` [PATCH v6 1/3] videodev2.h, v4l2-ioctl: add IPU3 raw10 color format Yong Zhi
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Yong Zhi @ 2017-10-26 2:24 UTC (permalink / raw)
To: linux-media, sakari.ailus
Cc: jian.xu.zheng, tfiga, rajmohan.mani, tuukka.toivonen,
hyungwoo.yang, chiranjeevi.rapolu, jerry.w.hu, Yong Zhi
The CIO2 driver exposes V4L2, V4L2 sub-device and Media controller interfaces
to the user space.
This series was tested on Kaby Lake based platform with 2 sensor configurations,
media topology was pasted at end for reference.
Link to user space implementation:
<URL:https://chromium.googlesource.com/chromiumos/platform/arc-camera/+/master>
===========
= history =
===========
version 6:
- Replaced NUM_FORMATS with ARRAY_SIZE(formats). (Sakari)
- cio2_fbpt_rearrange(): skip fbpt re-arrange when not needed.
- cio2_resume(): move cio2_fbpt_rearrange() to cio2_suspend() to avoid mem alloc
at resume time.(Sakari)
- cio2_pci_probe(): call cio2_fbpt_init_dummy() before v4l2_device_register().(Sakari)
- Fixed checkpatch.pl reported checks with --strict option.
- Added static keyword to cio2_queue_init().
- cio2_buffer_done(): assign ns in variable initialization.(Sakari)
- cio2_notifier_bound(): create hard link between cio2 queues and CSI-2 port.(Sakari)
- cio2_notifier_unbount(): remove obsolete comments.(Sakari)
- cio2_notifier_init(): return error when no subdevs found.(Sakari)
- cio2_find_queue_by_sensor_node(): remove the function (Tomasz Figa)
- ipu3-cio2.h:
Change CIO2_QUEUES 4 to match IPU3 capability.(Sakari)
Remove un-used "other_entries" in struct cio2_fbpt_entry.(Sakari)
- pixfmt-srggb10-ipu3.rst: Changed tabularcolumns to 4.(Sakari)
- Enable x86 32bit build (Sakari)
version 5:
- cio2_vb2_start_streaming():
- cio2_vb2_stop_streaming(): removed redundant call of csi2 sub-dev for s_stream.
- cio2_vb2_buf_queue(): disabled interrupts for the duration of the buf queue,
to prevent this code from being pre-empted, as suggested by Tomasz Figa,
to mitigate the effects of race conditions around vb2 buf queuing code.
Switched to a finite loop to check for the first free buffer and errored
out, when there are no buffers available. Removed calls to vb2_plane_vaddr()
Maintain correct buf queued count, in error cases.
- Implemented system sleep pm ops to support cio2 driver suspend/resume.
- Made the v4l2 buffer and SOF event use sequence from same source.
- cio2_vb2_queue_setup(): remove validating pixelformat suggested by Tomasz
Figa.
- cio2_v4l2_g_fmt()/cio2_v4l2_s_fmt(): seperated formats on sub-dev and video
device suggested by Sakari Ailus.
- cio2_v4l2_try_fmt(): seperated video node and subdev format in the get_fmt,
try_fmt and set_fmt callbacks.
- cio2_queue_event_sof(): added comments suggested by Hans Verkuil
- cio2_queue_init(): re-ordered q->subdev_pads settings. remove 4 lines for
quantization init.
- cio2_subdev_get_fmt(): get colorspace/xfer_func/ycbcr_enc/quantization
from sensor suggested by Hans Verkuil.
- cio2_fbpt_entry_init_buf(): stored offset of the first sg_list entry to
remove calls to vb2_plane_vaddr().
- cio2_subdev_open(): added new callback to intialize the try format.
- cio2_subdev_video_ops(): removed empty implementation suggested by Sakari Ailus.
- cio2_notifier_init(): added fwnode binding support for subdevices using
v4l2_async_notifier_parse_fwnode_endpoints()
Patch series v15 Unified fwnode endpoint parser, async sub-device notifier
support, N9 flash DTS is needed for the fwnode binding code to compile.
https://www.mail-archive.com/linux-media@vger.kernel.org/msg120239.html
This also requires the following patch (v1) for the fwnode binding to work
https://patchwork.kernel.org/patch/9986445/
- cio2_notifier_complete(): removed redundant call of
fwnode_graph_get_remote_endpoint() and fwnode_graph_parse_endpoint().
- Switched to Multi Plane APIs suggested by Tomasz Figa.
User space changes supporting multi plane APIs can be found here
https://chromium-review.googlesource.com/c/chromiumos/platform/arc-camera/+/683802
- ipu3-cio.h: moved macros out of struct cio2_fbpt_entry suggested by Hans Verkuil.
- cio2_hw_mbus_to_mipicode(): replaced with cio2_find_format().
- cio2_pci_probe(): cleaned up goto logic on error conditions suggested by Tomasz Figa.
- Fixed v4l2_compliance test failures
added 3 dummy function to pass v4l2_compliance test.
- Extended format example in pixfmt-srggb10-ipu3.rst to show DMA word boundary.
version 4:
- add cio2_video_link_validate() for video entity suggested by Sakari Ailus
- cio2_notifier_complete(): fix comments suggested by Sakari Ailus
- cio2_vb2_buf_queue(): fix the forever loop suggested by Tomasz Figa
- cio2_v4l2_querycap(): use vdev device_caps commented by Hans Verkuil
- cio2_vb2_buf_init(): allocate LOP table per page suggested by Tomasz Figa
- cio2_hw_init(): call cio2_csi2_calc_timing() earlier suggested by Tomasz Figa
- cio2_csi2_calc_timing(): add defalt settings for rx term/settle
- cio2_vb2_queue_setup(): remove num_planes checking suggested by Tomasz Figa
- cio2_buffer_done(): remove setting b->vbb.flags to V4L2_BUF_FLAG_DONE and
memset of vbb.timecode, also move vb2_set_plane_payload() to
cio2_vb2_buf_queue() suggested by Sakari Ailus
- cio2_queue_init(): export VB2_DMABUF io_modes suggested by Tomasz Figa
- cio2_vb2_return_all_buffers(): remove state from param list
suggested by Tomasz Figa
- cio2_vb2_buf_queue(): use vb2_is_streaming() instead of
vb2_start_streaming_called() suggested by Tomasz Figa
- cio2_pci_probe(): replace hard-coded linux driver version
suggested by Hans Verkuil
- ipu3-cio2.h: re-order the reg macros suggested by Sakari Ailus
- ipu3-cio2.h: add inline vb2q_to_cio2_queue() suggested by Sakari Ailus
- ipu3-cio2.h: add comments for CIO2_INT_IOC suggested by Tomasz Figa
- ipu3-cio2.h: adjust PBM watermark threshold from 53 to 48 (internal bugfix)
- run v4l2_compliance suggested by Hans Verkuil
Todo list:
- fix possible racy code in cio2_vb2_buf_queue()
- fix v4l2_compliance test failure
- switch to v4l2_pix_format_mplane API if future needs arise
version 3:
- remove cio2_set_power().
- replace dma_alloc_noncoherent() with dma_alloc_coherent().
- apply ffs tricks at possible places.
- change sensor_vc to local variable.
- move ktime_get_ns() a little earlier in the calling order.
- fix multiple assignments(I.e a = b =c)
- define CIO2_PAGE_SIZE for CIO2 PAGE_SIZE, SENSOR_VIR_CH_DFLT for default sensor virtual ch.
- rework cio2_csi2_calc_timing().
- update v4l2 async subdev field name from match.fwnode.fwn
to match.fwnode.fwnode.
- cherry-pick internal fix for triggering different irq on SOF and EOF.
- return -ENOMEM for vb2_dma_sg_plane_desc() in cio2_vb2_buf_init().
- add cio2_link_validate() placeholder for vdev.
version 2:
- remove all explicit DMA flush operations
- change dma_free_noncoherent() to dma_free_coherent()
- remove cio2_hw_mipi_lanes()
- replace v4l2_g_ext_ctrls() with v4l2_ctrl_g_ctrl()
in cio2_csi2_calc_timing().
- use ffs() to iterate the port_status in cio2_irq()
- add static inline file_to_cio2_queue() function
- comment dma_wmb(), cio2_rx_timing() and few other places
- use ktime_get_ns() for vb2_buf.timestamp in cio2_buffer_done()
- use of SET_RUNTIME_PM_OPS() macro for cio2_pm_ops
- use BIT() macro for bit difinitions
- remove un-used macros such as CIO2_QUEUE_WIDTH() in ipu3-cio2.h
- move the MODULE_AUTHOR() to the end of the file
- change file path to drivers/media/pci/intel/ipu3
version 1:
- Initial submission
Media device topology:
localhost bin # ./media-ctl -d /dev/media0 -p
Media controller API version 4.14.0
Media device information
------------------------
driver ipu3-cio2
model Intel IPU3 CIO2
serial
bus info PCI:0000:00:14.3
hw revision 0x0
driver version 4.14.0
Device topology
- entity 1: ipu3-csi2:0 (2 pads, 2 links)
type V4L2 subdev subtype Unknown flags 0
device node name /dev/v4l-subdev0
pad0: Sink
[fmt:SGRBG10_1X10/1936x1096 field:none]
<- "ov13858 8-0010":0 []
pad1: Source
[fmt:SGRBG10_1X10/1936x1096 field:none]
-> "ipu3-cio2:0":0 [ENABLED,IMMUTABLE]
- entity 4: ipu3-cio2:0 (1 pad, 1 link)
type Node subtype V4L flags 0
device node name /dev/video0
pad0: Sink
<- "ipu3-csi2:0":1 [ENABLED,IMMUTABLE]
- entity 10: ipu3-csi2:1 (2 pads, 2 links)
type V4L2 subdev subtype Unknown flags 0
device node name /dev/v4l-subdev1
pad0: Sink
[fmt:SGRBG10_1X10/1936x1096 field:none]
<- "ov5670 10-0036":0 []
pad1: Source
[fmt:SGRBG10_1X10/1936x1096 field:none]
-> "ipu3-cio2:1":0 [ENABLED,IMMUTABLE]
- entity 13: ipu3-cio2:1 (1 pad, 1 link)
type Node subtype V4L flags 0
device node name /dev/video1
pad0: Sink
<- "ipu3-csi2:1":1 [ENABLED,IMMUTABLE]
- entity 19: ipu3-csi2:2 (2 pads, 1 link)
type V4L2 subdev subtype Unknown flags 0
device node name /dev/v4l-subdev2
pad0: Sink
pad1: Source
[fmt:SGRBG10_1X10/1936x1096 field:none]
-> "ipu3-cio2:2":0 [ENABLED,IMMUTABLE]
- entity 22: ipu3-cio2:2 (1 pad, 1 link)
type Node subtype V4L flags 0
device node name /dev/video2
pad0: Sink
<- "ipu3-csi2:2":1 [ENABLED,IMMUTABLE]
- entity 28: ipu3-csi2:3 (2 pads, 1 link)
type V4L2 subdev subtype Unknown flags 0
device node name /dev/v4l-subdev3
pad0: Sink
pad1: Source
[fmt:SGRBG10_1X10/1936x1096 field:none]
-> "ipu3-cio2:3":0 [ENABLED,IMMUTABLE]
- entity 31: ipu3-cio2:3 (1 pad, 1 link)
type Node subtype V4L flags 0
device node name /dev/video3
pad0: Sink
<- "ipu3-csi2:3":1 [ENABLED,IMMUTABLE]
- entity 37: ov13858 8-0010 (1 pad, 1 link)
type V4L2 subdev subtype Sensor flags 0
device node name /dev/v4l-subdev4
pad0: Source
[fmt:SGRBG10_1X10/4224x3136 field:none]
-> "ipu3-csi2:0":0 []
- entity 39: dw9714 8-000c (0 pad, 0 link)
type V4L2 subdev subtype Lens flags 0
device node name /dev/v4l-subdev5
- entity 40: ov5670 10-0036 (1 pad, 1 link)
type V4L2 subdev subtype Sensor flags 0
device node name /dev/v4l-subdev6
pad0: Source
[fmt:SGRBG10_1X10/2592x1944 field:none]
-> "ipu3-csi2:1":0 []
localhost bin # ./v4l2-compliance -d /dev/video0
v4l2-compliance SHA : not available
Driver Info:
Driver name : ipu3-cio2
Card type : Intel IPU3 CIO2
Bus info : PCI:0000:00:14.3
Driver version: 4.14.0
Capabilities : 0x84201000
Video Capture Multiplanar
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04201000
Video Capture Multiplanar
Streaming
Extended Pix Format
Compliance test for device /dev/video0 (not using libv4l2):
Required ioctls:
test VIDIOC_QUERYCAP: OK
Allow for multiple opens:
test second video open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Test input 0:
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
test VIDIOC_QUERYCTRL: OK (Not Supported)
test VIDIOC_G/S_CTRL: OK (Not Supported)
test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 0 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test VIDIOC_EXPBUF: OK
Test input 0:
Total: 43, Succeeded: 43, Failed: 0, Warnings: 0
Note:
Same results with other 3 video nodes.
Running v4l2_compliance test with -f option will fail, we can only run
stream test with the help of media controller to link, configure and enable the
sub-dev/pads first.
Yong Zhi (3):
videodev2.h, v4l2-ioctl: add IPU3 raw10 color format
doc-rst: add IPU3 raw10 bayer pixel format definitions
intel-ipu3: cio2: Add new MIPI-CSI2 driver
Documentation/media/uapi/v4l/pixfmt-rgb.rst | 1 +
.../media/uapi/v4l/pixfmt-srggb10-ipu3.rst | 335 ++++
drivers/media/pci/Kconfig | 2 +
drivers/media/pci/Makefile | 3 +-
drivers/media/pci/intel/Makefile | 5 +
drivers/media/pci/intel/ipu3/Kconfig | 19 +
drivers/media/pci/intel/ipu3/Makefile | 1 +
drivers/media/pci/intel/ipu3/ipu3-cio2.c | 2014 ++++++++++++++++++++
drivers/media/pci/intel/ipu3/ipu3-cio2.h | 449 +++++
drivers/media/v4l2-core/v4l2-ioctl.c | 4 +
include/uapi/linux/videodev2.h | 6 +
11 files changed, 2838 insertions(+), 1 deletion(-)
create mode 100644 Documentation/media/uapi/v4l/pixfmt-srggb10-ipu3.rst
create mode 100644 drivers/media/pci/intel/Makefile
create mode 100644 drivers/media/pci/intel/ipu3/Kconfig
create mode 100644 drivers/media/pci/intel/ipu3/Makefile
create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.c
create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.h
--
2.7.4
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 1/3] videodev2.h, v4l2-ioctl: add IPU3 raw10 color format
2017-10-26 2:24 [PATCH v6 0/3] add IPU3 CIO2 CSI2 driver Yong Zhi
@ 2017-10-26 2:24 ` Yong Zhi
2017-10-26 2:24 ` [PATCH v6 2/3] doc-rst: add IPU3 raw10 bayer pixel format definitions Yong Zhi
2017-10-26 2:24 ` [PATCH v6 3/3] intel-ipu3: cio2: Add new MIPI-CSI2 driver Yong Zhi
2 siblings, 0 replies; 5+ messages in thread
From: Yong Zhi @ 2017-10-26 2:24 UTC (permalink / raw)
To: linux-media, sakari.ailus
Cc: jian.xu.zheng, tfiga, rajmohan.mani, tuukka.toivonen,
hyungwoo.yang, chiranjeevi.rapolu, jerry.w.hu, Yong Zhi
Add IPU3 specific formats:
V4L2_PIX_FMT_IPU3_SBGGR10
V4L2_PIX_FMT_IPU3_SGBRG10
V4L2_PIX_FMT_IPU3_SGRBG10
V4L2_PIX_FMT_IPU3_SRGGB10
Signed-off-by: Yong Zhi <yong.zhi@intel.com>
---
drivers/media/v4l2-core/v4l2-ioctl.c | 4 ++++
include/uapi/linux/videodev2.h | 6 ++++++
2 files changed, 10 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index 79614992ee21..3937945b12dc 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1202,6 +1202,10 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
case V4L2_PIX_FMT_SGBRG10P: descr = "10-bit Bayer GBGB/RGRG Packed"; break;
case V4L2_PIX_FMT_SGRBG10P: descr = "10-bit Bayer GRGR/BGBG Packed"; break;
case V4L2_PIX_FMT_SRGGB10P: descr = "10-bit Bayer RGRG/GBGB Packed"; break;
+ case V4L2_PIX_FMT_IPU3_SBGGR10: descr = "10-bit bayer BGGR IPU3 Packed"; break;
+ case V4L2_PIX_FMT_IPU3_SGBRG10: descr = "10-bit bayer GBRG IPU3 Packed"; break;
+ case V4L2_PIX_FMT_IPU3_SGRBG10: descr = "10-bit bayer GRBG IPU3 Packed"; break;
+ case V4L2_PIX_FMT_IPU3_SRGGB10: descr = "10-bit bayer RGGB IPU3 Packed"; break;
case V4L2_PIX_FMT_SBGGR10ALAW8: descr = "8-bit Bayer BGBG/GRGR (A-law)"; break;
case V4L2_PIX_FMT_SGBRG10ALAW8: descr = "8-bit Bayer GBGB/RGRG (A-law)"; break;
case V4L2_PIX_FMT_SGRBG10ALAW8: descr = "8-bit Bayer GRGR/BGBG (A-law)"; break;
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 185d6a0acc06..bcf6a50f6aac 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -668,6 +668,12 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_MT21C v4l2_fourcc('M', 'T', '2', '1') /* Mediatek compressed block mode */
#define V4L2_PIX_FMT_INZI v4l2_fourcc('I', 'N', 'Z', 'I') /* Intel Planar Greyscale 10-bit and Depth 16-bit */
+/* 10bit raw bayer 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 */
+#define V4L2_PIX_FMT_IPU3_SGBRG10 v4l2_fourcc('i', 'p', '3', 'g') /* IPU3 packed 10-bit GBRG bayer */
+#define V4L2_PIX_FMT_IPU3_SGRBG10 v4l2_fourcc('i', 'p', '3', 'G') /* IPU3 packed 10-bit GRBG bayer */
+#define V4L2_PIX_FMT_IPU3_SRGGB10 v4l2_fourcc('i', 'p', '3', 'r') /* IPU3 packed 10-bit RGGB bayer */
+
/* SDR formats - used only for Software Defined Radio devices */
#define V4L2_SDR_FMT_CU8 v4l2_fourcc('C', 'U', '0', '8') /* IQ u8 */
#define V4L2_SDR_FMT_CU16LE v4l2_fourcc('C', 'U', '1', '6') /* IQ u16le */
--
2.7.4
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v6 2/3] doc-rst: add IPU3 raw10 bayer pixel format definitions
2017-10-26 2:24 [PATCH v6 0/3] add IPU3 CIO2 CSI2 driver Yong Zhi
2017-10-26 2:24 ` [PATCH v6 1/3] videodev2.h, v4l2-ioctl: add IPU3 raw10 color format Yong Zhi
@ 2017-10-26 2:24 ` Yong Zhi
2017-10-26 2:24 ` [PATCH v6 3/3] intel-ipu3: cio2: Add new MIPI-CSI2 driver Yong Zhi
2 siblings, 0 replies; 5+ messages in thread
From: Yong Zhi @ 2017-10-26 2:24 UTC (permalink / raw)
To: linux-media, sakari.ailus
Cc: jian.xu.zheng, tfiga, rajmohan.mani, tuukka.toivonen,
hyungwoo.yang, chiranjeevi.rapolu, jerry.w.hu, Yong Zhi
The formats added by this patch are:
V4L2_PIX_FMT_IPU3_SBGGR10
V4L2_PIX_FMT_IPU3_SGBRG10
V4L2_PIX_FMT_IPU3_SGRBG10
V4L2_PIX_FMT_IPU3_SRGGB10
Signed-off-by: Hyungwoo Yang <hyungwoo.yang@intel.com>
Signed-off-by: Yong Zhi <yong.zhi@intel.com>
---
Documentation/media/uapi/v4l/pixfmt-rgb.rst | 1 +
.../media/uapi/v4l/pixfmt-srggb10-ipu3.rst | 335 +++++++++++++++++++++
2 files changed, 336 insertions(+)
create mode 100644 Documentation/media/uapi/v4l/pixfmt-srggb10-ipu3.rst
diff --git a/Documentation/media/uapi/v4l/pixfmt-rgb.rst b/Documentation/media/uapi/v4l/pixfmt-rgb.rst
index 4cc27195dc79..cf2ef7df9616 100644
--- a/Documentation/media/uapi/v4l/pixfmt-rgb.rst
+++ b/Documentation/media/uapi/v4l/pixfmt-rgb.rst
@@ -16,6 +16,7 @@ RGB Formats
pixfmt-srggb10p
pixfmt-srggb10alaw8
pixfmt-srggb10dpcm8
+ pixfmt-srggb10-ipu3
pixfmt-srggb12
pixfmt-srggb12p
pixfmt-srggb16
diff --git a/Documentation/media/uapi/v4l/pixfmt-srggb10-ipu3.rst b/Documentation/media/uapi/v4l/pixfmt-srggb10-ipu3.rst
new file mode 100644
index 000000000000..72fbd8f96381
--- /dev/null
+++ b/Documentation/media/uapi/v4l/pixfmt-srggb10-ipu3.rst
@@ -0,0 +1,335 @@
+.. -*- coding: utf-8; mode: rst -*-
+
+.. _V4L2_PIX_FMT_IPU3_SBGGR10:
+.. _V4L2_PIX_FMT_IPU3_SGBRG10:
+.. _V4L2_PIX_FMT_IPU3_SGRBG10:
+.. _V4L2_PIX_FMT_IPU3_SRGGB10:
+
+**********************************************************************************************************************************************
+V4L2_PIX_FMT_IPU3_SBGGR10 ('ip3b'), V4L2_PIX_FMT_IPU3_SGBRG10 ('ip3g'), V4L2_PIX_FMT_IPU3_SGRBG10 ('ip3G'), V4L2_PIX_FMT_IPU3_SRGGB10 ('ip3r')
+**********************************************************************************************************************************************
+
+10-bit Bayer formats
+
+Description
+===========
+
+These four pixel formats are used by Intel IPU3 driver, they are raw
+sRGB / Bayer formats with 10 bits per sample with every 25 pixels packed
+to 32 bytes leaving 6 most significant bits padding in the last byte.
+The format is little endian.
+
+In other respects this format is similar to :ref:`V4L2-PIX-FMT-SRGGB10`.
+Below is an example of a small image in V4L2_PIX_FMT_IPU3_SBGGR10 format.
+
+**Byte Order.**
+Each cell is one byte.
+
+.. tabularcolumns:: |p{0.8cm}|p{4.0cm}|p{4.0cm}|p{4.0cm}|p{4.0cm}|
+
+.. flat-table::
+
+ * - start + 0:
+ - B\ :sub:`0000low`
+ - G\ :sub:`0001low`\ (bits 7--2)
+
+ B\ :sub:`0000high`\ (bits 1--0)
+ - B\ :sub:`0002low`\ (bits 7--4)
+
+ G\ :sub:`0001high`\ (bits 3--0)
+ - G\ :sub:`0003low`\ (bits 7--6)
+
+ B\ :sub:`0002high`\ (bits 5--0)
+ * - start + 4:
+ - G\ :sub:`0003high`
+ - B\ :sub:`0004low`
+ - G\ :sub:`0005low`\ (bits 7--2)
+
+ B\ :sub:`0004high`\ (bits 1--0)
+ - B\ :sub:`0006low`\ (bits 7--4)
+
+ G\ :sub:`0005high`\ (bits 3--0)
+ * - start + 8:
+ - G\ :sub:`0007low`\ (bits 7--6)
+
+ B\ :sub:`0006high`\ (bits 5--0)
+ - G\ :sub:`0007high`
+ - B\ :sub:`0008low`
+ - G\ :sub:`0009low`\ (bits 7--2)
+
+ B\ :sub:`0008high`\ (bits 1--0)
+ * - start + 12:
+ - B\ :sub:`0010low`\ (bits 7--4)
+
+ G\ :sub:`0009high`\ (bits 3--0)
+ - G\ :sub:`0011low`\ (bits 7--6)
+
+ B\ :sub:`0010high`\ (bits 5--0)
+ - G\ :sub:`0011high`
+ - B\ :sub:`0012low`
+ * - start + 16:
+ - G\ :sub:`0013low`\ (bits 7--2)
+
+ B\ :sub:`0012high`\ (bits 1--0)
+ - B\ :sub:`0014low`\ (bits 7--4)
+
+ G\ :sub:`0013high`\ (bits 3--0)
+ - G\ :sub:`0015low`\ (bits 7--6)
+
+ B\ :sub:`0014high`\ (bits 5--0)
+ - G\ :sub:`0015high`
+ * - start + 20
+ - B\ :sub:`0016low`
+ - G\ :sub:`0017low`\ (bits 7--2)
+
+ B\ :sub:`0016high`\ (bits 1--0)
+ - B\ :sub:`0018low`\ (bits 7--4)
+
+ G\ :sub:`0017high`\ (bits 3--0)
+ - G\ :sub:`0019low`\ (bits 7--6)
+
+ B\ :sub:`0018high`\ (bits 5--0)
+ * - start + 24:
+ - G\ :sub:`0019high`
+ - B\ :sub:`0020low`
+ - G\ :sub:`0021low`\ (bits 7--2)
+
+ B\ :sub:`0020high`\ (bits 1--0)
+ - B\ :sub:`0022low`\ (bits 7--4)
+
+ G\ :sub:`0021high`\ (bits 3--0)
+ * - start + 28:
+ - G\ :sub:`0023low`\ (bits 7--6)
+
+ B\ :sub:`0022high`\ (bits 5--0)
+ - G\ :sub:`0023high`
+ - B\ :sub:`0024low`
+ - B\ :sub:`0024high`\ (bits 1--0)
+ * - start + 32:
+ - G\ :sub:`0100low`
+ - R\ :sub:`0101low`\ (bits 7--2)
+
+ G\ :sub:`0100high`\ (bits 1--0)
+ - G\ :sub:`0102low`\ (bits 7--4)
+
+ R\ :sub:`0101high`\ (bits 3--0)
+ - R\ :sub:`0103low`\ (bits 7--6)
+
+ G\ :sub:`0102high`\ (bits 5--0)
+ * - start + 36:
+ - R\ :sub:`0103high`
+ - G\ :sub:`0104low`
+ - R\ :sub:`0105low`\ (bits 7--2)
+
+ G\ :sub:`0104high`\ (bits 1--0)
+ - G\ :sub:`0106low`\ (bits 7--4)
+
+ R\ :sub:`0105high`\ (bits 3--0)
+ * - start + 40:
+ - R\ :sub:`0107low`\ (bits 7--6)
+
+ G\ :sub:`0106high`\ (bits 5--0)
+ - R\ :sub:`0107high`
+ - G\ :sub:`0108low`
+ - R\ :sub:`0109low`\ (bits 7--2)
+
+ G\ :sub:`0108high`\ (bits 1--0)
+ * - start + 44:
+ - G\ :sub:`0110low`\ (bits 7--4)
+
+ R\ :sub:`0109high`\ (bits 3--0)
+ - R\ :sub:`0111low`\ (bits 7--6)
+
+ G\ :sub:`0110high`\ (bits 5--0)
+ - R\ :sub:`0111high`
+ - G\ :sub:`0112low`
+ * - start + 48:
+ - R\ :sub:`0113low`\ (bits 7--2)
+
+ G\ :sub:`0112high`\ (bits 1--0)
+ - G\ :sub:`0114low`\ (bits 7--4)
+
+ R\ :sub:`0113high`\ (bits 3--0)
+ - R\ :sub:`0115low`\ (bits 7--6)
+
+ G\ :sub:`0114high`\ (bits 5--0)
+ - R\ :sub:`0115high`
+ * - start + 52:
+ - G\ :sub:`0116low`
+ - R\ :sub:`0117low`\ (bits 7--2)
+
+ G\ :sub:`0116high`\ (bits 1--0)
+ - G\ :sub:`0118low`\ (bits 7--4)
+
+ R\ :sub:`0117high`\ (bits 3--0)
+ - R\ :sub:`0119low`\ (bits 7--6)
+
+ G\ :sub:`0118high`\ (bits 5--0)
+ * - start + 56:
+ - R\ :sub:`0119high`
+ - G\ :sub:`0120low`
+ - R\ :sub:`0121low`\ (bits 7--2)
+
+ G\ :sub:`0120high`\ (bits 1--0)
+ - G\ :sub:`0122low`\ (bits 7--4)
+
+ R\ :sub:`0121high`\ (bits 3--0)
+ * - start + 60:
+ - R\ :sub:`0123low`\ (bits 7--6)
+
+ G\ :sub:`0122high`\ (bits 5--0)
+ - R\ :sub:`0123high`
+ - G\ :sub:`0124low`
+ - G\ :sub:`0124high`\ (bits 1--0)
+ * - start + 64:
+ - B\ :sub:`0200low`
+ - G\ :sub:`0201low`\ (bits 7--2)
+
+ B\ :sub:`0200high`\ (bits 1--0)
+ - B\ :sub:`0202low`\ (bits 7--4)
+
+ G\ :sub:`0201high`\ (bits 3--0)
+ - G\ :sub:`0203low`\ (bits 7--6)
+
+ B\ :sub:`0202high`\ (bits 5--0)
+ * - start + 68:
+ - G\ :sub:`0203high`
+ - B\ :sub:`0204low`
+ - G\ :sub:`0205low`\ (bits 7--2)
+
+ B\ :sub:`0204high`\ (bits 1--0)
+ - B\ :sub:`0206low`\ (bits 7--4)
+
+ G\ :sub:`0205high`\ (bits 3--0)
+ * - start + 72:
+ - G\ :sub:`0207low`\ (bits 7--6)
+
+ B\ :sub:`0206high`\ (bits 5--0)
+ - G\ :sub:`0207high`
+ - B\ :sub:`0208low`
+ - G\ :sub:`0209low`\ (bits 7--2)
+
+ B\ :sub:`0208high`\ (bits 1--0)
+ * - start + 76:
+ - B\ :sub:`0210low`\ (bits 7--4)
+
+ G\ :sub:`0209high`\ (bits 3--0)
+ - G\ :sub:`0211low`\ (bits 7--6)
+
+ B\ :sub:`0210high`\ (bits 5--0)
+ - G\ :sub:`0211high`
+ - B\ :sub:`0212low`
+ * - start + 80:
+ - G\ :sub:`0213low`\ (bits 7--2)
+
+ B\ :sub:`0212high`\ (bits 1--0)
+ - B\ :sub:`0214low`\ (bits 7--4)
+
+ G\ :sub:`0213high`\ (bits 3--0)
+ - G\ :sub:`0215low`\ (bits 7--6)
+
+ B\ :sub:`0214high`\ (bits 5--0)
+ - G\ :sub:`0215high`
+ * - start + 84:
+ - B\ :sub:`0216low`
+ - G\ :sub:`0217low`\ (bits 7--2)
+
+ B\ :sub:`0216high`\ (bits 1--0)
+ - B\ :sub:`0218low`\ (bits 7--4)
+
+ G\ :sub:`0217high`\ (bits 3--0)
+ - G\ :sub:`0219low`\ (bits 7--6)
+
+ B\ :sub:`0218high`\ (bits 5--0)
+ * - start + 88:
+ - G\ :sub:`0219high`
+ - B\ :sub:`0220low`
+ - G\ :sub:`0221low`\ (bits 7--2)
+
+ B\ :sub:`0220high`\ (bits 1--0)
+ - B\ :sub:`0222low`\ (bits 7--4)
+
+ G\ :sub:`0221high`\ (bits 3--0)
+ * - start + 92:
+ - G\ :sub:`0223low`\ (bits 7--6)
+
+ B\ :sub:`0222high`\ (bits 5--0)
+ - G\ :sub:`0223high`
+ - B\ :sub:`0224low`
+ - B\ :sub:`0224high`\ (bits 1--0)
+ * - start + 96:
+ - G\ :sub:`0300low`
+ - R\ :sub:`0301low`\ (bits 7--2)
+
+ G\ :sub:`0300high`\ (bits 1--0)
+ - G\ :sub:`0302low`\ (bits 7--4)
+
+ R\ :sub:`0301high`\ (bits 3--0)
+ - R\ :sub:`0303low`\ (bits 7--6)
+
+ G\ :sub:`0302high`\ (bits 5--0)
+ * - start + 100:
+ - R\ :sub:`0303high`
+ - G\ :sub:`0304low`
+ - R\ :sub:`0305low`\ (bits 7--2)
+
+ G\ :sub:`0304high`\ (bits 1--0)
+ - G\ :sub:`0306low`\ (bits 7--4)
+
+ R\ :sub:`0305high`\ (bits 3--0)
+ * - start + 104:
+ - R\ :sub:`0307low`\ (bits 7--6)
+
+ G\ :sub:`0306high`\ (bits 5--0)
+ - R\ :sub:`0307high`
+ - G\ :sub:`0308low`
+ - R\ :sub:`0309low`\ (bits 7--2)
+
+ G\ :sub:`0308high`\ (bits 1--0)
+ * - start + 108:
+ - G\ :sub:`0310low`\ (bits 7--4)
+
+ R\ :sub:`0309high`\ (bits 3--0)
+ - R\ :sub:`0311low`\ (bits 7--6)
+
+ G\ :sub:`0310high`\ (bits 5--0)
+ - R\ :sub:`0311high`
+ - G\ :sub:`0312low`
+ * - start + 112:
+ - R\ :sub:`0313low`\ (bits 7--2)
+
+ G\ :sub:`0312high`\ (bits 1--0)
+ - G\ :sub:`0314low`\ (bits 7--4)
+
+ R\ :sub:`0313high`\ (bits 3--0)
+ - R\ :sub:`0315low`\ (bits 7--6)
+
+ G\ :sub:`0314high`\ (bits 5--0)
+ - R\ :sub:`0315high`
+ * - start + 116:
+ - G\ :sub:`0316low`
+ - R\ :sub:`0317low`\ (bits 7--2)
+
+ G\ :sub:`0316high`\ (bits 1--0)
+ - G\ :sub:`0318low`\ (bits 7--4)
+
+ R\ :sub:`0317high`\ (bits 3--0)
+ - R\ :sub:`0319low`\ (bits 7--6)
+
+ G\ :sub:`0318high`\ (bits 5--0)
+ * - start + 120:
+ - R\ :sub:`0319high`
+ - G\ :sub:`0320low`
+ - R\ :sub:`0321low`\ (bits 7--2)
+
+ G\ :sub:`0320high`\ (bits 1--0)
+ - G\ :sub:`0322low`\ (bits 7--4)
+
+ R\ :sub:`0321high`\ (bits 3--0)
+ * - start + 124:
+ - R\ :sub:`0323low`\ (bits 7--6)
+
+ G\ :sub:`0322high`\ (bits 5--0)
+ - R\ :sub:`0323high`
+ - G\ :sub:`0324low`
+ - G\ :sub:`0324high`\ (bits 1--0)
--
2.7.4
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v6 3/3] intel-ipu3: cio2: Add new MIPI-CSI2 driver
2017-10-26 2:24 [PATCH v6 0/3] add IPU3 CIO2 CSI2 driver Yong Zhi
2017-10-26 2:24 ` [PATCH v6 1/3] videodev2.h, v4l2-ioctl: add IPU3 raw10 color format Yong Zhi
2017-10-26 2:24 ` [PATCH v6 2/3] doc-rst: add IPU3 raw10 bayer pixel format definitions Yong Zhi
@ 2017-10-26 2:24 ` Yong Zhi
2017-10-26 14:42 ` Sakari Ailus
2 siblings, 1 reply; 5+ messages in thread
From: Yong Zhi @ 2017-10-26 2:24 UTC (permalink / raw)
To: linux-media, sakari.ailus
Cc: jian.xu.zheng, tfiga, rajmohan.mani, tuukka.toivonen,
hyungwoo.yang, chiranjeevi.rapolu, jerry.w.hu, Yong Zhi,
Vijaykumar Ramya
This patch adds CIO2 CSI-2 device driver for
Intel's IPU3 camera sub-system support.
Signed-off-by: Yong Zhi <yong.zhi@intel.com>
Signed-off-by: Hyungwoo Yang <hyungwoo.yang@intel.com>
Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
Signed-off-by: Vijaykumar Ramya <ramya.vijaykumar@intel.com>
---
drivers/media/pci/Kconfig | 2 +
drivers/media/pci/Makefile | 3 +-
drivers/media/pci/intel/Makefile | 5 +
drivers/media/pci/intel/ipu3/Kconfig | 19 +
drivers/media/pci/intel/ipu3/Makefile | 1 +
drivers/media/pci/intel/ipu3/ipu3-cio2.c | 2014 ++++++++++++++++++++++++++++++
drivers/media/pci/intel/ipu3/ipu3-cio2.h | 449 +++++++
7 files changed, 2492 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/pci/intel/Makefile
create mode 100644 drivers/media/pci/intel/ipu3/Kconfig
create mode 100644 drivers/media/pci/intel/ipu3/Makefile
create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.c
create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.h
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index da28e68c87d8..5932e225f9c0 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -54,5 +54,7 @@ source "drivers/media/pci/smipcie/Kconfig"
source "drivers/media/pci/netup_unidvb/Kconfig"
endif
+source "drivers/media/pci/intel/ipu3/Kconfig"
+
endif #MEDIA_PCI_SUPPORT
endif #PCI
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index a7e8af0f64a7..d8f98434fb8c 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -13,7 +13,8 @@ obj-y += ttpci/ \
ddbridge/ \
saa7146/ \
smipcie/ \
- netup_unidvb/
+ netup_unidvb/ \
+ intel/
obj-$(CONFIG_VIDEO_IVTV) += ivtv/
obj-$(CONFIG_VIDEO_ZORAN) += zoran/
diff --git a/drivers/media/pci/intel/Makefile b/drivers/media/pci/intel/Makefile
new file mode 100644
index 000000000000..745c8b2a7819
--- /dev/null
+++ b/drivers/media/pci/intel/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the IPU3 cio2 and ImGU drivers
+#
+
+obj-y += ipu3/
diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig
new file mode 100644
index 000000000000..4bb8e0e97e49
--- /dev/null
+++ b/drivers/media/pci/intel/ipu3/Kconfig
@@ -0,0 +1,19 @@
+config VIDEO_IPU3_CIO2
+ tristate "Intel ipu3-cio2 driver"
+ depends on VIDEO_V4L2 && PCI
+ depends on VIDEO_V4L2_SUBDEV_API
+ depends on MEDIA_CONTROLLER
+ depends on X86 || COMPILE_TEST
+ depends on HAS_DMA
+ depends on ACPI
+ select V4L2_FWNODE
+ select VIDEOBUF2_DMA_SG
+
+ ---help---
+ This is the Intel IPU3 CIO2 CSI-2 receiver unit, found in Intel
+ Skylake and Kaby Lake SoCs and used for capturing images and
+ video from a camera sensor.
+
+ Say Y or M here if you have a Skylake/Kaby Lake SoC with MIPI CSI-2
+ connected camera.
+ The module will be called ipu3-cio2.
diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile
new file mode 100644
index 000000000000..20186e3ff2ae
--- /dev/null
+++ b/drivers/media/pci/intel/ipu3/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o
diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
new file mode 100644
index 000000000000..8c0feda77c74
--- /dev/null
+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
@@ -0,0 +1,2014 @@
+/*
+ * Copyright (c) 2017 Intel Corporation.
+ *
+ * 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.
+ *
+ * Based partially on Intel IPU4 driver written by
+ * Sakari Ailus <sakari.ailus@linux.intel.com>
+ * Samu Onkalo <samu.onkalo@intel.com>
+ * Jouni Högander <jouni.hogander@intel.com>
+ * Jouni Ukkonen <jouni.ukkonen@intel.com>
+ * Antti Laakso <antti.laakso@intel.com>
+ * et al.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/vmalloc.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "ipu3-cio2.h"
+
+struct ipu3_cio2_fmt {
+ u32 mbus_code;
+ u32 fourcc;
+ u8 mipicode;
+};
+
+/*
+ * These are raw formats used in Intel's third generation of
+ * Image Processing Unit known as IPU3.
+ * 10bit raw bayer packed, 32 bytes for every 25 pixels,
+ * last LSB 6 bits unused.
+ */
+static const struct ipu3_cio2_fmt formats[] = {
+ { /* put default entry at beginning */
+ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .fourcc = V4L2_PIX_FMT_IPU3_SGRBG10,
+ .mipicode = 0x2b,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .fourcc = V4L2_PIX_FMT_IPU3_SGBRG10,
+ .mipicode = 0x2b,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .fourcc = V4L2_PIX_FMT_IPU3_SBGGR10,
+ .mipicode = 0x2b,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .fourcc = V4L2_PIX_FMT_IPU3_SRGGB10,
+ .mipicode = 0x2b,
+ },
+};
+
+/*
+ * cio2_find_format - lookup color format by fourcc or/and media bus code
+ * @pixelformat: fourcc to match, ignored if null
+ * @mbus_code: media bus code to match, ignored if null
+ */
+static const struct ipu3_cio2_fmt *cio2_find_format(const u32 *pixelformat,
+ const u32 *mbus_code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (pixelformat && *pixelformat != formats[i].fourcc)
+ continue;
+ if (mbus_code && *mbus_code != formats[i].mbus_code)
+ continue;
+
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static inline u32 cio2_bytesperline(const unsigned int width)
+{
+ /*
+ * 64 bytes for every 50 pixels, the line length
+ * in bytes is multiple of 64 (line end alignment).
+ */
+ return DIV_ROUND_UP(width, 50) * 64;
+}
+
+/**************** FBPT operations ****************/
+
+static void cio2_fbpt_exit_dummy(struct cio2_device *cio2)
+{
+ if (cio2->dummy_lop) {
+ dma_free_coherent(&cio2->pci_dev->dev, CIO2_PAGE_SIZE,
+ cio2->dummy_lop, cio2->dummy_lop_bus_addr);
+ cio2->dummy_lop = NULL;
+ }
+ if (cio2->dummy_page) {
+ dma_free_coherent(&cio2->pci_dev->dev, CIO2_PAGE_SIZE,
+ cio2->dummy_page, cio2->dummy_page_bus_addr);
+ cio2->dummy_page = NULL;
+ }
+}
+
+static int cio2_fbpt_init_dummy(struct cio2_device *cio2)
+{
+ unsigned int i;
+
+ cio2->dummy_page = dma_alloc_coherent(&cio2->pci_dev->dev,
+ CIO2_PAGE_SIZE,
+ &cio2->dummy_page_bus_addr,
+ GFP_KERNEL);
+ cio2->dummy_lop = dma_alloc_coherent(&cio2->pci_dev->dev,
+ CIO2_PAGE_SIZE,
+ &cio2->dummy_lop_bus_addr,
+ GFP_KERNEL);
+ if (!cio2->dummy_page || !cio2->dummy_lop) {
+ cio2_fbpt_exit_dummy(cio2);
+ return -ENOMEM;
+ }
+ /*
+ * List of Pointers(LOP) contains 1024x32b pointers to 4KB page each
+ * Initialize each entry to dummy_page bus base address.
+ */
+ for (i = 0; i < CIO2_PAGE_SIZE / sizeof(*cio2->dummy_lop); i++)
+ cio2->dummy_lop[i] = cio2->dummy_page_bus_addr >> PAGE_SHIFT;
+
+ return 0;
+}
+
+static void cio2_fbpt_entry_enable(struct cio2_device *cio2,
+ struct cio2_fbpt_entry entry[CIO2_MAX_LOPS])
+{
+ /*
+ * The CPU first initializes some fields in fbpt, then sets
+ * the VALID bit, this barrier is to ensure that the DMA(device)
+ * does not see the VALID bit enabled before other fields are
+ * initialized; otherwise it could lead to havoc.
+ */
+ dma_wmb();
+
+ /*
+ * Request interrupts for start and completion
+ * Valid bit is applicable only to 1st entry
+ */
+ entry[0].first_entry.ctrl = CIO2_FBPT_CTRL_VALID |
+ CIO2_FBPT_CTRL_IOC | CIO2_FBPT_CTRL_IOS;
+}
+
+/* Initialize fpbt entries to point to dummy frame */
+static void cio2_fbpt_entry_init_dummy(struct cio2_device *cio2,
+ struct cio2_fbpt_entry
+ entry[CIO2_MAX_LOPS])
+{
+ unsigned int i;
+
+ entry[0].first_entry.first_page_offset = 0;
+ entry[1].second_entry.num_of_pages =
+ CIO2_PAGE_SIZE / sizeof(u32) * CIO2_MAX_LOPS;
+ entry[1].second_entry.last_page_available_bytes = CIO2_PAGE_SIZE - 1;
+
+ for (i = 0; i < CIO2_MAX_LOPS; i++)
+ entry[i].lop_page_addr = cio2->dummy_lop_bus_addr >> PAGE_SHIFT;
+
+ cio2_fbpt_entry_enable(cio2, entry);
+}
+
+/* Initialize fpbt entries to point to a given buffer */
+static void cio2_fbpt_entry_init_buf(struct cio2_device *cio2,
+ struct cio2_buffer *b,
+ struct cio2_fbpt_entry
+ entry[CIO2_MAX_LOPS])
+{
+ struct vb2_buffer *vb = &b->vbb.vb2_buf;
+ unsigned int length = vb->planes[0].length;
+ int remaining, i;
+
+ entry[0].first_entry.first_page_offset = b->offset;
+ remaining = length + entry[0].first_entry.first_page_offset;
+ entry[1].second_entry.num_of_pages =
+ DIV_ROUND_UP(remaining, CIO2_PAGE_SIZE);
+ /*
+ * last_page_available_bytes has the offset of the last byte in the
+ * last page which is still accessible by DMA. DMA cannot access
+ * beyond this point. Valid range for this is from 0 to 4095.
+ * 0 indicates 1st byte in the page is DMA accessible.
+ * 4095 (CIO2_PAGE_SIZE - 1) means every single byte in the last page
+ * is available for DMA transfer.
+ */
+ entry[1].second_entry.last_page_available_bytes =
+ (remaining & ~PAGE_MASK) ?
+ (remaining & ~PAGE_MASK) - 1 :
+ CIO2_PAGE_SIZE - 1;
+ /* Fill FBPT */
+ remaining = length;
+ i = 0;
+ while (remaining > 0) {
+ entry->lop_page_addr = b->lop_bus_addr[i] >> PAGE_SHIFT;
+ remaining -= CIO2_PAGE_SIZE / sizeof(u32) * CIO2_PAGE_SIZE;
+ entry++;
+ i++;
+ }
+
+ /*
+ * The first not meaningful FBPT entry should point to a valid LOP
+ */
+ entry->lop_page_addr = cio2->dummy_lop_bus_addr >> PAGE_SHIFT;
+
+ cio2_fbpt_entry_enable(cio2, entry);
+}
+
+static int cio2_fbpt_init(struct cio2_device *cio2, struct cio2_queue *q)
+{
+ struct device *dev = &cio2->pci_dev->dev;
+
+ q->fbpt = dma_alloc_coherent(dev, CIO2_FBPT_SIZE, &q->fbpt_bus_addr,
+ GFP_KERNEL);
+ if (!q->fbpt)
+ return -ENOMEM;
+
+ memset(q->fbpt, 0, CIO2_FBPT_SIZE);
+
+ return 0;
+}
+
+static int cio2_fbpt_rearrange(struct cio2_device *cio2, struct cio2_queue *q)
+{
+ struct cio2_fbpt_entry *fbpt_temp;
+ int i, j;
+ int size, entries;
+ struct cio2_buffer *bufs[CIO2_MAX_BUFFERS];
+
+ for (i = 0, j = q->bufs_first; i < CIO2_MAX_BUFFERS;
+ i++, j = (j + 1) % CIO2_MAX_BUFFERS)
+ if (q->bufs[j])
+ break;
+
+ if (i == CIO2_MAX_BUFFERS)
+ return 0;
+
+ if (j) {
+ fbpt_temp = vmalloc(CIO2_FBPT_SIZE);
+ if (!fbpt_temp)
+ return -ENOMEM;
+
+ entries = (CIO2_MAX_BUFFERS - j) * CIO2_MAX_LOPS;
+ size = entries * sizeof(struct cio2_fbpt_entry);
+
+ memcpy(fbpt_temp, q->fbpt + j * CIO2_MAX_LOPS, size);
+ memcpy(fbpt_temp + entries, q->fbpt, CIO2_FBPT_SIZE - size);
+ memcpy(q->fbpt, fbpt_temp, CIO2_FBPT_SIZE);
+ memcpy(bufs, q->bufs + j,
+ (CIO2_MAX_BUFFERS - j) * sizeof(*bufs));
+ memcpy(bufs + CIO2_MAX_BUFFERS - j, q->bufs, j * sizeof(*bufs));
+ memcpy(q->bufs, bufs, CIO2_MAX_BUFFERS * sizeof(*bufs));
+
+ vfree(fbpt_temp);
+ }
+
+ /*
+ * DMA clears the valid bit when accessing the buffer.
+ * When stopping stream in suspend callback, some of the buffers
+ * may be in invalid state. After resume, when DMA meets the invalid
+ * buffer, it will halt and stop receiving new data.
+ * To avoid DMA halting, set the valid bit for all buffers in FBPT.
+ */
+ for (i = 0; i < CIO2_MAX_BUFFERS; i++)
+ cio2_fbpt_entry_enable(cio2, q->fbpt + i * CIO2_MAX_LOPS);
+
+ return 0;
+}
+
+static void cio2_fbpt_exit(struct cio2_queue *q, struct device *dev)
+{
+ dma_free_coherent(dev, CIO2_FBPT_SIZE, q->fbpt, q->fbpt_bus_addr);
+}
+
+/**************** CSI2 hardware setup ****************/
+
+/*
+ * The CSI2 receiver has several parameters affecting
+ * the receiver timings. These depend on the MIPI bus frequency
+ * F in Hz (sensor transmitter rate) as follows:
+ * register value = (A/1e9 + B * UI) / COUNT_ACC
+ * where
+ * UI = 1 / (2 * F) in seconds
+ * COUNT_ACC = counter accuracy in seconds
+ * For IPU3 COUNT_ACC = 0.0625
+ *
+ * A and B are coefficients from the table below,
+ * depending whether the register minimum or maximum value is
+ * calculated.
+ * Minimum Maximum
+ * Clock lane A B A B
+ * reg_rx_csi_dly_cnt_termen_clane 0 0 38 0
+ * reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16
+ * Data lanes
+ * reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6
+ * reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6
+ * reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6
+ * reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6
+ *
+ * We use the minimum values of both A and B.
+ */
+
+/*
+ * shift for keeping value range suitable for 32-bit integer arithmetics
+ */
+#define LIMIT_SHIFT 8
+
+static s32 cio2_rx_timing(s32 a, s32 b, s64 freq, int def)
+{
+ const u32 accinv = 16; /* invert of counter resolution */
+ const u32 uiinv = 500000000; /* 1e9 / 2 */
+ s32 r;
+
+ freq >>= LIMIT_SHIFT;
+
+ if (WARN_ON(freq <= 0 || freq > S32_MAX))
+ return def;
+ /*
+ * b could be 0, -2 or -8, so |accinv * b| is always
+ * less than (1 << ds) and thus |r| < 500000000.
+ */
+ r = accinv * b * (uiinv >> LIMIT_SHIFT);
+ r = r / (s32)freq;
+ /* max value of a is 95 */
+ r += accinv * a;
+
+ return r;
+};
+
+/* Calculate the the delay value for termination enable of clock lane HS Rx */
+static int cio2_csi2_calc_timing(struct cio2_device *cio2, struct cio2_queue *q,
+ struct cio2_csi2_timing *timing)
+{
+ struct device *dev = &cio2->pci_dev->dev;
+ struct v4l2_querymenu qm = {.id = V4L2_CID_LINK_FREQ, };
+ struct v4l2_ctrl *link_freq;
+ s64 freq;
+ int r;
+
+ if (!q->sensor)
+ return -ENODEV;
+
+ link_freq = v4l2_ctrl_find(q->sensor->ctrl_handler, V4L2_CID_LINK_FREQ);
+ if (!link_freq) {
+ dev_err(dev, "failed to find LINK_FREQ\n");
+ return -EPIPE;
+ };
+
+ qm.index = v4l2_ctrl_g_ctrl(link_freq);
+ r = v4l2_querymenu(q->sensor->ctrl_handler, &qm);
+ if (r) {
+ dev_err(dev, "failed to get menu item\n");
+ return r;
+ }
+
+ if (!qm.value) {
+ dev_err(dev, "error invalid link_freq\n");
+ return -EINVAL;
+ }
+ freq = qm.value;
+
+ timing->clk_termen = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_A,
+ CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_B,
+ freq,
+ CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT);
+ timing->clk_settle = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_A,
+ CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_B,
+ freq,
+ CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT);
+ timing->dat_termen = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_A,
+ CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_B,
+ freq,
+ CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT);
+ timing->dat_settle = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_A,
+ CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_B,
+ freq,
+ CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT);
+
+ dev_dbg(dev, "freq ct value is %d\n", timing->clk_termen);
+ dev_dbg(dev, "freq cs value is %d\n", timing->clk_settle);
+ dev_dbg(dev, "freq dt value is %d\n", timing->dat_termen);
+ dev_dbg(dev, "freq ds value is %d\n", timing->dat_settle);
+
+ return 0;
+};
+
+static int cio2_hw_init(struct cio2_device *cio2, struct cio2_queue *q)
+{
+ static const int NUM_VCS = 4;
+ static const int SID; /* Stream id */
+ static const int ENTRY;
+ static const int FBPT_WIDTH = DIV_ROUND_UP(CIO2_MAX_LOPS,
+ CIO2_FBPT_SUBENTRY_UNIT);
+ const u32 num_buffers1 = CIO2_MAX_BUFFERS - 1;
+ const struct ipu3_cio2_fmt *fmt;
+ void __iomem *const base = cio2->base;
+ u8 lanes, csi2bus = q->csi2.port;
+ u8 sensor_vc = SENSOR_VIR_CH_DFLT;
+ struct cio2_csi2_timing timing;
+ int i, r;
+
+ fmt = cio2_find_format(NULL, &q->subdev_fmt.code);
+ if (!fmt)
+ return -EINVAL;
+
+ lanes = q->csi2.lanes;
+
+ r = cio2_csi2_calc_timing(cio2, q, &timing);
+ if (r)
+ return r;
+
+ writel(timing.clk_termen, q->csi_rx_base +
+ CIO2_REG_CSIRX_DLY_CNT_TERMEN(CIO2_CSIRX_DLY_CNT_CLANE_IDX));
+ writel(timing.clk_settle, q->csi_rx_base +
+ CIO2_REG_CSIRX_DLY_CNT_SETTLE(CIO2_CSIRX_DLY_CNT_CLANE_IDX));
+
+ for (i = 0; i < lanes; i++) {
+ writel(timing.dat_termen, q->csi_rx_base +
+ CIO2_REG_CSIRX_DLY_CNT_TERMEN(i));
+ writel(timing.dat_settle, q->csi_rx_base +
+ CIO2_REG_CSIRX_DLY_CNT_SETTLE(i));
+ }
+
+ writel(CIO2_PBM_WMCTRL1_MIN_2CK |
+ CIO2_PBM_WMCTRL1_MID1_2CK |
+ CIO2_PBM_WMCTRL1_MID2_2CK, base + CIO2_REG_PBM_WMCTRL1);
+ writel(CIO2_PBM_WMCTRL2_HWM_2CK << CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT |
+ CIO2_PBM_WMCTRL2_LWM_2CK << CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT |
+ CIO2_PBM_WMCTRL2_OBFFWM_2CK <<
+ CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT |
+ CIO2_PBM_WMCTRL2_TRANSDYN << CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT |
+ CIO2_PBM_WMCTRL2_OBFF_MEM_EN, base + CIO2_REG_PBM_WMCTRL2);
+ writel(CIO2_PBM_ARB_CTRL_LANES_DIV <<
+ CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT |
+ CIO2_PBM_ARB_CTRL_LE_EN |
+ CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN <<
+ CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT |
+ CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP <<
+ CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT,
+ base + CIO2_REG_PBM_ARB_CTRL);
+ writel(CIO2_CSIRX_STATUS_DLANE_HS_MASK,
+ q->csi_rx_base + CIO2_REG_CSIRX_STATUS_DLANE_HS);
+ writel(CIO2_CSIRX_STATUS_DLANE_LP_MASK,
+ q->csi_rx_base + CIO2_REG_CSIRX_STATUS_DLANE_LP);
+
+ writel(CIO2_FB_HPLL_FREQ, base + CIO2_REG_FB_HPLL_FREQ);
+ writel(CIO2_ISCLK_RATIO, base + CIO2_REG_ISCLK_RATIO);
+
+ /* Configure MIPI backend */
+ for (i = 0; i < NUM_VCS; i++)
+ writel(1, q->csi_rx_base + CIO2_REG_MIPIBE_SP_LUT_ENTRY(i));
+
+ /* There are 16 short packet LUT entry */
+ for (i = 0; i < 16; i++)
+ writel(CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD,
+ q->csi_rx_base + CIO2_REG_MIPIBE_LP_LUT_ENTRY(i));
+ writel(CIO2_MIPIBE_GLOBAL_LUT_DISREGARD,
+ q->csi_rx_base + CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD);
+
+ writel(CIO2_INT_EN_EXT_IE_MASK, base + CIO2_REG_INT_EN_EXT_IE);
+ writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_MASK);
+ writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_ENABLE);
+ writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_EDGE);
+ writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_LEVEL_NOT_PULSE);
+ writel(CIO2_INT_EN_EXT_OE_MASK, base + CIO2_REG_INT_EN_EXT_OE);
+
+ writel(CIO2_REG_INT_EN_IRQ | CIO2_INT_IOC(CIO2_DMA_CHAN) |
+ CIO2_REG_INT_EN_IOS(CIO2_DMA_CHAN),
+ base + CIO2_REG_INT_EN);
+
+ writel((CIO2_PXM_PXF_FMT_CFG_BPP_10 | CIO2_PXM_PXF_FMT_CFG_PCK_64B)
+ << CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT,
+ base + CIO2_REG_PXM_PXF_FMT_CFG0(csi2bus));
+ writel(SID << CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT |
+ sensor_vc << CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT |
+ fmt->mipicode << CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT,
+ q->csi_rx_base + CIO2_REG_MIPIBE_LP_LUT_ENTRY(ENTRY));
+ writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_COMP_FORMAT(sensor_vc));
+ writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_FORCE_RAW8);
+ writel(0, base + CIO2_REG_PXM_SID2BID0(csi2bus));
+
+ writel(lanes, q->csi_rx_base + CIO2_REG_CSIRX_NOF_ENABLED_LANES);
+ writel(CIO2_CGC_PRIM_TGE |
+ CIO2_CGC_SIDE_TGE |
+ CIO2_CGC_XOSC_TGE |
+ CIO2_CGC_D3I3_TGE |
+ CIO2_CGC_CSI2_INTERFRAME_TGE |
+ CIO2_CGC_CSI2_PORT_DCGE |
+ CIO2_CGC_SIDE_DCGE |
+ CIO2_CGC_PRIM_DCGE |
+ CIO2_CGC_ROSC_DCGE |
+ CIO2_CGC_XOSC_DCGE |
+ CIO2_CGC_CLKGATE_HOLDOFF << CIO2_CGC_CLKGATE_HOLDOFF_SHIFT |
+ CIO2_CGC_CSI_CLKGATE_HOLDOFF
+ << CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT, base + CIO2_REG_CGC);
+ writel(CIO2_LTRCTRL_LTRDYNEN, base + CIO2_REG_LTRCTRL);
+ writel(CIO2_LTRVAL0_VAL << CIO2_LTRVAL02_VAL_SHIFT |
+ CIO2_LTRVAL0_SCALE << CIO2_LTRVAL02_SCALE_SHIFT |
+ CIO2_LTRVAL1_VAL << CIO2_LTRVAL13_VAL_SHIFT |
+ CIO2_LTRVAL1_SCALE << CIO2_LTRVAL13_SCALE_SHIFT,
+ base + CIO2_REG_LTRVAL01);
+ writel(CIO2_LTRVAL2_VAL << CIO2_LTRVAL02_VAL_SHIFT |
+ CIO2_LTRVAL2_SCALE << CIO2_LTRVAL02_SCALE_SHIFT |
+ CIO2_LTRVAL3_VAL << CIO2_LTRVAL13_VAL_SHIFT |
+ CIO2_LTRVAL3_SCALE << CIO2_LTRVAL13_SCALE_SHIFT,
+ base + CIO2_REG_LTRVAL23);
+
+ for (i = 0; i < CIO2_NUM_DMA_CHAN; i++) {
+ writel(0, base + CIO2_REG_CDMABA(i));
+ writel(0, base + CIO2_REG_CDMAC0(i));
+ writel(0, base + CIO2_REG_CDMAC1(i));
+ }
+
+ /* Enable DMA */
+ writel(q->fbpt_bus_addr >> PAGE_SHIFT,
+ base + CIO2_REG_CDMABA(CIO2_DMA_CHAN));
+
+ writel(num_buffers1 << CIO2_CDMAC0_FBPT_LEN_SHIFT |
+ FBPT_WIDTH << CIO2_CDMAC0_FBPT_WIDTH_SHIFT |
+ CIO2_CDMAC0_DMA_INTR_ON_FE |
+ CIO2_CDMAC0_FBPT_UPDATE_FIFO_FULL |
+ CIO2_CDMAC0_DMA_EN |
+ CIO2_CDMAC0_DMA_INTR_ON_FS |
+ CIO2_CDMAC0_DMA_HALTED, base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN));
+
+ writel(1 << CIO2_CDMAC1_LINENUMUPDATE_SHIFT,
+ base + CIO2_REG_CDMAC1(CIO2_DMA_CHAN));
+
+ writel(0, base + CIO2_REG_PBM_FOPN_ABORT);
+
+ writel(CIO2_PXM_FRF_CFG_CRC_TH << CIO2_PXM_FRF_CFG_CRC_TH_SHIFT |
+ CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR |
+ CIO2_PXM_FRF_CFG_MSK_ECC_RE |
+ CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE,
+ base + CIO2_REG_PXM_FRF_CFG(q->csi2.port));
+
+ /* Clear interrupts */
+ writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_CLEAR);
+ writel(~0, base + CIO2_REG_INT_STS_EXT_OE);
+ writel(~0, base + CIO2_REG_INT_STS_EXT_IE);
+ writel(~0, base + CIO2_REG_INT_STS);
+
+ /* Enable devices, starting from the last device in the pipe */
+ writel(1, q->csi_rx_base + CIO2_REG_MIPIBE_ENABLE);
+ writel(1, q->csi_rx_base + CIO2_REG_CSIRX_ENABLE);
+
+ return 0;
+}
+
+static void cio2_hw_exit(struct cio2_device *cio2, struct cio2_queue *q)
+{
+ void __iomem *base = cio2->base;
+ unsigned int i, maxloops = 1000;
+
+ /* Disable CSI receiver and MIPI backend devices */
+ writel(0, q->csi_rx_base + CIO2_REG_CSIRX_ENABLE);
+ writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_ENABLE);
+
+ /* Halt DMA */
+ writel(0, base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN));
+ do {
+ if (readl(base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN)) &
+ CIO2_CDMAC0_DMA_HALTED)
+ break;
+ usleep_range(1000, 2000);
+ } while (--maxloops);
+ if (!maxloops)
+ dev_err(&cio2->pci_dev->dev,
+ "DMA %i can not be halted\n", CIO2_DMA_CHAN);
+
+ for (i = 0; i < CIO2_NUM_PORTS; i++) {
+ writel(readl(base + CIO2_REG_PXM_FRF_CFG(i)) |
+ CIO2_PXM_FRF_CFG_ABORT, base + CIO2_REG_PXM_FRF_CFG(i));
+ writel(readl(base + CIO2_REG_PBM_FOPN_ABORT) |
+ CIO2_PBM_FOPN_ABORT(i), base + CIO2_REG_PBM_FOPN_ABORT);
+ }
+}
+
+static void cio2_buffer_done(struct cio2_device *cio2, unsigned int dma_chan)
+{
+ struct device *dev = &cio2->pci_dev->dev;
+ struct cio2_queue *q = cio2->cur_queue;
+ int buffers_found = 0;
+ u64 ns = ktime_get_ns();
+
+ if (dma_chan >= CIO2_QUEUES) {
+ dev_err(dev, "bad DMA channel %i\n", dma_chan);
+ return;
+ }
+
+ /* Find out which buffer(s) are ready */
+ do {
+ struct cio2_fbpt_entry *const entry =
+ &q->fbpt[q->bufs_first * CIO2_MAX_LOPS];
+ struct cio2_buffer *b;
+
+ if (entry->first_entry.ctrl & CIO2_FBPT_CTRL_VALID)
+ break;
+
+ b = q->bufs[q->bufs_first];
+ if (b) {
+ unsigned int bytes = entry[1].second_entry.num_of_bytes;
+
+ q->bufs[q->bufs_first] = NULL;
+ atomic_dec(&q->bufs_queued);
+ dev_dbg(&cio2->pci_dev->dev,
+ "buffer %i done\n", b->vbb.vb2_buf.index);
+
+ b->vbb.vb2_buf.timestamp = ns;
+ b->vbb.field = V4L2_FIELD_NONE;
+ b->vbb.sequence = atomic_read(&q->frame_sequence);
+ if (b->vbb.vb2_buf.planes[0].length != bytes)
+ dev_warn(dev, "buffer length is %d received %d\n",
+ b->vbb.vb2_buf.planes[0].length,
+ bytes);
+ vb2_buffer_done(&b->vbb.vb2_buf, VB2_BUF_STATE_DONE);
+ }
+ atomic_inc(&q->frame_sequence);
+ cio2_fbpt_entry_init_dummy(cio2, entry);
+ q->bufs_first = (q->bufs_first + 1) % CIO2_MAX_BUFFERS;
+ buffers_found++;
+ } while (1);
+
+ if (buffers_found == 0)
+ dev_warn(&cio2->pci_dev->dev,
+ "no ready buffers found on DMA channel %u\n",
+ dma_chan);
+}
+
+static void cio2_queue_event_sof(struct cio2_device *cio2, struct cio2_queue *q)
+{
+ /*
+ * For the user space camera control algorithms it is essential
+ * to know when the reception of a frame has begun. That's often
+ * the best timing information to get from the hardware.
+ */
+ struct v4l2_event event = {
+ .type = V4L2_EVENT_FRAME_SYNC,
+ .u.frame_sync.frame_sequence = atomic_read(&q->frame_sequence),
+ };
+
+ v4l2_event_queue(q->subdev.devnode, &event);
+}
+
+static const char *const cio2_irq_errs[] = {
+ "single packet header error corrected",
+ "multiple packet header errors detected",
+ "payload checksum (CRC) error",
+ "fifo overflow",
+ "reserved short packet data type detected",
+ "reserved long packet data type detected",
+ "incomplete long packet detected",
+ "frame sync error",
+ "line sync error",
+ "DPHY start of transmission error",
+ "DPHY synchronization error",
+ "escape mode error",
+ "escape mode trigger event",
+ "escape mode ultra-low power state for data lane(s)",
+ "escape mode ultra-low power state exit for clock lane",
+ "inter-frame short packet discarded",
+ "inter-frame long packet discarded",
+ "non-matching Long Packet stalled",
+};
+
+static const char *const cio2_port_errs[] = {
+ "ECC recoverable",
+ "DPHY not recoverable",
+ "ECC not recoverable",
+ "CRC error",
+ "INTERFRAMEDATA",
+ "PKT2SHORT",
+ "PKT2LONG",
+};
+
+static irqreturn_t cio2_irq(int irq, void *cio2_ptr)
+{
+ struct cio2_device *cio2 = cio2_ptr;
+ void __iomem *const base = cio2->base;
+ struct device *dev = &cio2->pci_dev->dev;
+ u32 int_status, int_clear;
+
+ int_status = readl(base + CIO2_REG_INT_STS);
+ int_clear = int_status;
+
+ if (!int_status)
+ return IRQ_NONE;
+
+ if (int_status & CIO2_INT_IOOE) {
+ /*
+ * Interrupt on Output Error:
+ * 1) SRAM is full and FS received, or
+ * 2) An invalid bit detected by DMA.
+ */
+ u32 oe_status, oe_clear;
+
+ oe_clear = readl(base + CIO2_REG_INT_STS_EXT_OE);
+ oe_status = oe_clear;
+
+ if (oe_status & CIO2_INT_EXT_OE_DMAOE_MASK) {
+ dev_err(dev, "DMA output error: 0x%x\n",
+ (oe_status & CIO2_INT_EXT_OE_DMAOE_MASK)
+ >> CIO2_INT_EXT_OE_DMAOE_SHIFT);
+ oe_status &= ~CIO2_INT_EXT_OE_DMAOE_MASK;
+ }
+ if (oe_status & CIO2_INT_EXT_OE_OES_MASK) {
+ dev_err(dev, "DMA output error on CSI2 buses: 0x%x\n",
+ (oe_status & CIO2_INT_EXT_OE_OES_MASK)
+ >> CIO2_INT_EXT_OE_OES_SHIFT);
+ oe_status &= ~CIO2_INT_EXT_OE_OES_MASK;
+ }
+ writel(oe_clear, base + CIO2_REG_INT_STS_EXT_OE);
+ if (oe_status)
+ dev_warn(dev, "unknown interrupt 0x%x on OE\n",
+ oe_status);
+ int_status &= ~CIO2_INT_IOOE;
+ }
+
+ if (int_status & CIO2_INT_IOC_MASK) {
+ /* DMA IO done -- frame ready */
+ u32 clr = 0;
+ unsigned int d;
+
+ for (d = 0; d < CIO2_NUM_DMA_CHAN; d++)
+ if (int_status & CIO2_INT_IOC(d)) {
+ clr |= CIO2_INT_IOC(d);
+ cio2_buffer_done(cio2, d);
+ }
+ int_status &= ~clr;
+ }
+
+ if (int_status & CIO2_INT_IOS_IOLN_MASK) {
+ /* DMA IO starts or reached specified line */
+ u32 clr = 0;
+ unsigned int d;
+
+ for (d = 0; d < CIO2_NUM_DMA_CHAN; d++)
+ if (int_status & CIO2_INT_IOS_IOLN(d)) {
+ clr |= CIO2_INT_IOS_IOLN(d);
+ if (d == CIO2_DMA_CHAN)
+ cio2_queue_event_sof(cio2,
+ cio2->cur_queue);
+ }
+ int_status &= ~clr;
+ }
+
+ if (int_status & (CIO2_INT_IOIE | CIO2_INT_IOIRQ)) {
+ /* CSI2 receiver (error) interrupt */
+ u32 ie_status, ie_clear;
+ unsigned int port;
+
+ ie_clear = readl(base + CIO2_REG_INT_STS_EXT_IE);
+ ie_status = ie_clear;
+
+ for (port = 0; port < CIO2_NUM_PORTS; port++) {
+ u32 port_status = (ie_status >> (port * 8)) & 0xff;
+ u32 err_mask = BIT_MASK(ARRAY_SIZE(cio2_port_errs)) - 1;
+ void __iomem *const csi_rx_base =
+ base + CIO2_REG_PIPE_BASE(port);
+ unsigned int i;
+
+ while (port_status & err_mask) {
+ i = ffs(port_status) - 1;
+ dev_err(dev, "port %i error %s\n",
+ port, cio2_port_errs[i]);
+ ie_status &= ~BIT(port * 8 + i);
+ port_status &= ~BIT(i);
+ }
+
+ if (ie_status & CIO2_INT_EXT_IE_IRQ(port)) {
+ u32 csi2_status, csi2_clear;
+
+ csi2_status = readl(csi_rx_base +
+ CIO2_REG_IRQCTRL_STATUS);
+ csi2_clear = csi2_status;
+ err_mask =
+ BIT_MASK(ARRAY_SIZE(cio2_irq_errs)) - 1;
+
+ while (csi2_status & err_mask) {
+ i = ffs(csi2_status) - 1;
+ dev_err(dev,
+ "CSI-2 receiver port %i: %s\n",
+ port, cio2_irq_errs[i]);
+ csi2_status &= ~BIT(i);
+ }
+
+ writel(csi2_clear,
+ csi_rx_base + CIO2_REG_IRQCTRL_CLEAR);
+ if (csi2_status)
+ dev_warn(dev,
+ "unknown CSI2 error 0x%x on port %i\n",
+ csi2_status, port);
+
+ ie_status &= ~CIO2_INT_EXT_IE_IRQ(port);
+ }
+ }
+
+ writel(ie_clear, base + CIO2_REG_INT_STS_EXT_IE);
+ if (ie_status)
+ dev_warn(dev, "unknown interrupt 0x%x on IE\n",
+ ie_status);
+
+ int_status &= ~(CIO2_INT_IOIE | CIO2_INT_IOIRQ);
+ }
+
+ writel(int_clear, base + CIO2_REG_INT_STS);
+ if (int_status)
+ dev_warn(dev, "unknown interrupt 0x%x on INT\n", int_status);
+
+ return IRQ_HANDLED;
+}
+
+/**************** Videobuf2 interface ****************/
+
+static void cio2_vb2_return_all_buffers(struct cio2_queue *q)
+{
+ unsigned int i;
+
+ for (i = 0; i < CIO2_MAX_BUFFERS; i++) {
+ if (q->bufs[i]) {
+ atomic_dec(&q->bufs_queued);
+ vb2_buffer_done(&q->bufs[i]->vbb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ }
+ }
+}
+
+static int cio2_vb2_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct cio2_device *cio2 = vb2_get_drv_priv(vq);
+ struct cio2_queue *q = vb2q_to_cio2_queue(vq);
+ unsigned int i;
+
+ *num_planes = q->format.num_planes;
+
+ for (i = 0; i < *num_planes; ++i) {
+ sizes[i] = q->format.plane_fmt[i].sizeimage;
+ alloc_devs[i] = &cio2->pci_dev->dev;
+ }
+
+ *num_buffers = clamp_val(*num_buffers, 1, CIO2_MAX_BUFFERS);
+
+ /* Initialize buffer queue */
+ for (i = 0; i < CIO2_MAX_BUFFERS; i++) {
+ q->bufs[i] = NULL;
+ cio2_fbpt_entry_init_dummy(cio2, &q->fbpt[i * CIO2_MAX_LOPS]);
+ }
+ atomic_set(&q->bufs_queued, 0);
+ q->bufs_first = 0;
+ q->bufs_next = 0;
+
+ return 0;
+}
+
+/* Called after each buffer is allocated */
+static int cio2_vb2_buf_init(struct vb2_buffer *vb)
+{
+ struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue);
+ struct device *dev = &cio2->pci_dev->dev;
+ struct cio2_buffer *b =
+ container_of(vb, struct cio2_buffer, vbb.vb2_buf);
+ static const unsigned int entries_per_page =
+ CIO2_PAGE_SIZE / sizeof(u32);
+ unsigned int pages = DIV_ROUND_UP(vb->planes[0].length, CIO2_PAGE_SIZE);
+ unsigned int lops = DIV_ROUND_UP(pages + 1, entries_per_page);
+ struct sg_table *sg;
+ struct sg_page_iter sg_iter;
+ unsigned int i, j;
+
+ if (lops <= 0 || lops > CIO2_MAX_LOPS) {
+ dev_err(dev, "%s: bad buffer size (%i)\n", __func__,
+ vb->planes[0].length);
+ return -ENOSPC; /* Should never happen */
+ }
+
+ memset(b->lop, 0, sizeof(*b->lop));
+ /* Allocate LOP table */
+ for (i = 0; i < lops; i++) {
+ b->lop[i] = dma_alloc_coherent(dev, CIO2_PAGE_SIZE,
+ &b->lop_bus_addr[i], GFP_KERNEL);
+ if (!b->lop[i])
+ goto fail;
+ }
+
+ /* Fill LOP */
+ sg = vb2_dma_sg_plane_desc(vb, 0);
+ if (!sg)
+ return -ENOMEM;
+
+ if (sg->nents && sg->sgl)
+ b->offset = sg->sgl->offset;
+
+ i = j = 0;
+ for_each_sg_page(sg->sgl, &sg_iter, sg->nents, 0) {
+ b->lop[i][j] = sg_page_iter_dma_address(&sg_iter) >> PAGE_SHIFT;
+ j++;
+ if (j == entries_per_page) {
+ i++;
+ j = 0;
+ }
+ }
+
+ b->lop[i][j] = cio2->dummy_page_bus_addr >> PAGE_SHIFT;
+ return 0;
+fail:
+ for (; i >= 0; i--)
+ dma_free_coherent(dev, CIO2_PAGE_SIZE,
+ b->lop[i], b->lop_bus_addr[i]);
+ return -ENOMEM;
+}
+
+/* Transfer buffer ownership to cio2 */
+static void cio2_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue);
+ struct cio2_queue *q =
+ container_of(vb->vb2_queue, struct cio2_queue, vbq);
+ struct cio2_buffer *b =
+ container_of(vb, struct cio2_buffer, vbb.vb2_buf);
+ struct cio2_fbpt_entry *entry;
+ unsigned long flags;
+ unsigned int i, j, next = q->bufs_next;
+ int bufs_queued = atomic_inc_return(&q->bufs_queued);
+ u32 fbpt_rp;
+
+ dev_dbg(&cio2->pci_dev->dev, "queue buffer %d\n", vb->index);
+
+ /*
+ * This code queues the buffer to the CIO2 DMA engine, which starts
+ * running once streaming has started. It is possible that this code
+ * gets pre-empted due to increased CPU load. Upon this, the driver
+ * does not get an opportunity to queue new buffers to the CIO2 DMA
+ * engine. When the DMA engine encounters an FBPT entry without the
+ * VALID bit set, the DMA engine halts, which requires a restart of
+ * the DMA engine and sensor, to continue streaming.
+ * This is not desired and is highly unlikely given that there are
+ * 32 FBPT entries that the DMA engine needs to process, to run into
+ * an FBPT entry, without the VALID bit set. We try to mitigate this
+ * by disabling interrupts for the duration of this queueing.
+ */
+ local_irq_save(flags);
+
+ fbpt_rp = (readl(cio2->base + CIO2_REG_CDMARI(CIO2_DMA_CHAN))
+ >> CIO2_CDMARI_FBPT_RP_SHIFT)
+ & CIO2_CDMARI_FBPT_RP_MASK;
+
+ /*
+ * fbpt_rp is the fbpt entry that the dma is currently working
+ * on, but since it could jump to next entry at any time,
+ * assume that we might already be there.
+ */
+ fbpt_rp = (fbpt_rp + 1) % CIO2_MAX_BUFFERS;
+
+ if (bufs_queued <= 1 || fbpt_rp == next)
+ /* Buffers were drained */
+ next = (fbpt_rp + 1) % CIO2_MAX_BUFFERS;
+
+ for (i = 0; i < CIO2_MAX_BUFFERS; i++) {
+ /*
+ * We have allocated CIO2_MAX_BUFFERS circularly for the
+ * hw, the user has requested N buffer queue. The driver
+ * ensures N <= CIO2_MAX_BUFFERS and guarantees that whenever
+ * user queues a buffer, there necessarily is a free buffer.
+ */
+ if (!q->bufs[next]) {
+ q->bufs[next] = b;
+ entry = &q->fbpt[next * CIO2_MAX_LOPS];
+ cio2_fbpt_entry_init_buf(cio2, b, entry);
+ local_irq_restore(flags);
+ q->bufs_next = (next + 1) % CIO2_MAX_BUFFERS;
+ for (j = 0; j < vb->num_planes; j++)
+ vb2_set_plane_payload(vb, j,
+ q->format.plane_fmt[j].sizeimage);
+ return;
+ }
+
+ dev_dbg(&cio2->pci_dev->dev, "entry %i was full!\n", next);
+ next = (next + 1) % CIO2_MAX_BUFFERS;
+ }
+
+ local_irq_restore(flags);
+ dev_err(&cio2->pci_dev->dev, "error: all cio2 entries were full!\n");
+ atomic_dec(&q->bufs_queued);
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+}
+
+/* Called when each buffer is freed */
+static void cio2_vb2_buf_cleanup(struct vb2_buffer *vb)
+{
+ struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue);
+ struct cio2_buffer *b =
+ container_of(vb, struct cio2_buffer, vbb.vb2_buf);
+ unsigned int i;
+
+ /* Free LOP table */
+ for (i = 0; i < CIO2_MAX_LOPS; i++) {
+ if (b->lop[i])
+ dma_free_coherent(&cio2->pci_dev->dev, CIO2_PAGE_SIZE,
+ b->lop[i], b->lop_bus_addr[i]);
+ }
+}
+
+static int cio2_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct cio2_queue *q = vb2q_to_cio2_queue(vq);
+ struct cio2_device *cio2 = vb2_get_drv_priv(vq);
+ int r;
+
+ cio2->cur_queue = q;
+ atomic_set(&q->frame_sequence, 0);
+
+ r = pm_runtime_get_sync(&cio2->pci_dev->dev);
+ if (r < 0) {
+ dev_info(&cio2->pci_dev->dev, "failed to set power %d\n", r);
+ pm_runtime_put(&cio2->pci_dev->dev);
+ return r;
+ }
+
+ r = media_pipeline_start(&q->vdev.entity, &q->pipe);
+ if (r)
+ goto fail_pipeline;
+
+ r = cio2_hw_init(cio2, q);
+ if (r)
+ goto fail_hw;
+
+ /* Start streaming on sensor */
+ r = v4l2_subdev_call(q->sensor, video, s_stream, 1);
+ if (r)
+ goto fail_csi2_subdev;
+
+ cio2->streaming = true;
+
+ return 0;
+
+fail_csi2_subdev:
+ cio2_hw_exit(cio2, q);
+fail_hw:
+ media_pipeline_stop(&q->vdev.entity);
+fail_pipeline:
+ dev_dbg(&cio2->pci_dev->dev, "failed to start streaming (%d)\n", r);
+ cio2_vb2_return_all_buffers(q);
+
+ return r;
+}
+
+static void cio2_vb2_stop_streaming(struct vb2_queue *vq)
+{
+ struct cio2_queue *q = vb2q_to_cio2_queue(vq);
+ struct cio2_device *cio2 = vb2_get_drv_priv(vq);
+
+ if (v4l2_subdev_call(q->sensor, video, s_stream, 0))
+ dev_err(&cio2->pci_dev->dev,
+ "failed to stop sensor streaming\n");
+
+ cio2_hw_exit(cio2, q);
+ cio2_vb2_return_all_buffers(q);
+ media_pipeline_stop(&q->vdev.entity);
+ pm_runtime_put(&cio2->pci_dev->dev);
+ cio2->streaming = false;
+}
+
+static const struct vb2_ops cio2_vb2_ops = {
+ .buf_init = cio2_vb2_buf_init,
+ .buf_queue = cio2_vb2_buf_queue,
+ .buf_cleanup = cio2_vb2_buf_cleanup,
+ .queue_setup = cio2_vb2_queue_setup,
+ .start_streaming = cio2_vb2_start_streaming,
+ .stop_streaming = cio2_vb2_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+/**************** V4L2 interface ****************/
+
+static int cio2_v4l2_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct cio2_device *cio2 = video_drvdata(file);
+
+ strlcpy(cap->driver, CIO2_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, CIO2_DEVICE_NAME, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "PCI:%s", pci_name(cio2->pci_dev));
+
+ return 0;
+}
+
+static int cio2_v4l2_enum_fmt(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index >= ARRAY_SIZE(formats))
+ return -EINVAL;
+
+ f->pixelformat = formats[f->index].fourcc;
+
+ return 0;
+}
+
+/* The format is validated in cio2_video_link_validate() */
+static int cio2_v4l2_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct cio2_queue *q = file_to_cio2_queue(file);
+
+ f->fmt.pix_mp = q->format;
+
+ return 0;
+}
+
+static int cio2_v4l2_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ const struct ipu3_cio2_fmt *fmt;
+ struct v4l2_pix_format_mplane *mpix = &f->fmt.pix_mp;
+
+ fmt = cio2_find_format(&mpix->pixelformat, NULL);
+ if (!fmt)
+ fmt = &formats[0];
+
+ /* Only supports up to 4224x3136 */
+ if (mpix->width > CIO2_IMAGE_MAX_WIDTH)
+ mpix->width = CIO2_IMAGE_MAX_WIDTH;
+ if (mpix->height > CIO2_IMAGE_MAX_LENGTH)
+ mpix->height = CIO2_IMAGE_MAX_LENGTH;
+
+ mpix->num_planes = 1;
+ mpix->pixelformat = fmt->fourcc;
+ mpix->colorspace = V4L2_COLORSPACE_RAW;
+ mpix->field = V4L2_FIELD_NONE;
+ memset(mpix->reserved, 0, sizeof(mpix->reserved));
+ mpix->plane_fmt[0].bytesperline = cio2_bytesperline(mpix->width);
+ mpix->plane_fmt[0].sizeimage = mpix->plane_fmt[0].bytesperline *
+ mpix->height;
+ memset(mpix->plane_fmt[0].reserved, 0,
+ sizeof(mpix->plane_fmt[0].reserved));
+
+ /* use default */
+ mpix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ mpix->quantization = V4L2_QUANTIZATION_DEFAULT;
+ mpix->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ return 0;
+}
+
+static int cio2_v4l2_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct cio2_queue *q = file_to_cio2_queue(file);
+
+ cio2_v4l2_try_fmt(file, fh, f);
+ q->format = f->fmt.pix_mp;
+
+ return 0;
+}
+
+static int
+cio2_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
+{
+ if (input->index > 0)
+ return -EINVAL;
+
+ strlcpy(input->name, "camera", sizeof(input->name));
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+
+ return 0;
+}
+
+static int
+cio2_video_g_input(struct file *file, void *fh, unsigned int *input)
+{
+ *input = 0;
+
+ return 0;
+}
+
+static int
+cio2_video_s_input(struct file *file, void *fh, unsigned int input)
+{
+ return input == 0 ? 0 : -EINVAL;
+}
+
+static const struct v4l2_file_operations cio2_v4l2_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = video_ioctl2,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+};
+
+static const struct v4l2_ioctl_ops cio2_v4l2_ioctl_ops = {
+ .vidioc_querycap = cio2_v4l2_querycap,
+ .vidioc_enum_fmt_vid_cap_mplane = cio2_v4l2_enum_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = cio2_v4l2_g_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = cio2_v4l2_s_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = cio2_v4l2_try_fmt,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_enum_input = cio2_video_enum_input,
+ .vidioc_g_input = cio2_video_g_input,
+ .vidioc_s_input = cio2_video_s_input,
+};
+
+static int cio2_subdev_subscribe_event(struct v4l2_subdev *sd,
+ struct v4l2_fh *fh,
+ struct v4l2_event_subscription *sub)
+{
+ if (sub->type != V4L2_EVENT_FRAME_SYNC)
+ return -EINVAL;
+
+ /* Line number. For now only zero accepted. */
+ if (sub->id != 0)
+ return -EINVAL;
+
+ return v4l2_event_subscribe(fh, sub, 0, NULL);
+}
+
+static int cio2_subdev_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_mbus_framefmt fmt;
+ struct v4l2_mbus_framefmt *format;
+ static const u32 default_width = 1936;
+ static const u32 default_height = 1096;
+
+ /* Initialize try_fmt */
+ format = v4l2_subdev_get_try_format(sd, fh->pad, CIO2_PAD_SINK);
+ fmt.width = default_width;
+ fmt.height = default_height;
+ fmt.code = formats[0].mbus_code;
+ fmt.field = V4L2_FIELD_NONE;
+ fmt.colorspace = V4L2_COLORSPACE_RAW;
+ fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
+ fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+ *format = fmt;
+
+ /* same as sink */
+ format = v4l2_subdev_get_try_format(sd, fh->pad, CIO2_PAD_SOURCE);
+ *format = fmt;
+
+ return 0;
+}
+
+/*
+ * cio2_subdev_get_fmt - Handle get format by pads subdev method
+ * @sd : pointer to v4l2 subdev structure
+ * @cfg: V4L2 subdev pad config
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int cio2_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct cio2_queue *q = container_of(sd, struct cio2_queue, subdev);
+ struct v4l2_subdev_format sensor_fmt;
+ int ret;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
+ return 0;
+ }
+
+ if (fmt->pad == CIO2_PAD_SINK) {
+ sensor_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(q->sensor, pad, get_fmt, NULL,
+ &sensor_fmt);
+ if (ret)
+ return ret;
+ /* update colorspace etc from sensor */
+ q->subdev_fmt.colorspace = sensor_fmt.format.colorspace;
+ q->subdev_fmt.ycbcr_enc = sensor_fmt.format.ycbcr_enc;
+ q->subdev_fmt.quantization = sensor_fmt.format.quantization;
+ q->subdev_fmt.xfer_func = sensor_fmt.format.xfer_func;
+ }
+
+ fmt->format = q->subdev_fmt;
+
+ return 0;
+}
+
+/*
+ * cio2_subdev_set_fmt - Handle set format by pads subdev method
+ * @sd : pointer to v4l2 subdev structure
+ * @cfg: V4L2 subdev pad config
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int cio2_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct cio2_queue *q = container_of(sd, struct cio2_queue, subdev);
+
+ /*
+ * Only allow setting sink pad format;
+ * source always propagates from sink
+ */
+ if (fmt->pad == CIO2_PAD_SOURCE)
+ return cio2_subdev_get_fmt(sd, cfg, fmt);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
+ } else {
+ /* It's the sink, allow changing frame size */
+ q->subdev_fmt.width = fmt->format.width;
+ q->subdev_fmt.height = fmt->format.height;
+ q->subdev_fmt.code = fmt->format.code;
+ fmt->format = q->subdev_fmt;
+ }
+
+ return 0;
+}
+
+static int cio2_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(formats))
+ return -EINVAL;
+
+ code->code = formats[code->index].mbus_code;
+ return 0;
+}
+
+static int cio2_subdev_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 cio2_video_link_validate(struct media_link *link)
+{
+ struct video_device *vd = container_of(link->sink->entity,
+ struct video_device, entity);
+ struct cio2_queue *q = container_of(vd, struct cio2_queue, vdev);
+ struct cio2_device *cio2 = video_get_drvdata(vd);
+ struct v4l2_subdev_format source_fmt;
+ int ret;
+
+ if (!media_entity_remote_pad(link->sink->entity->pads)) {
+ dev_info(&cio2->pci_dev->dev,
+ "video node %s pad not connected\n", vd->name);
+ return -ENOTCONN;
+ }
+
+ ret = cio2_subdev_link_validate_get_format(link->source, &source_fmt);
+ if (ret < 0)
+ return 0;
+
+ if (source_fmt.format.width != q->format.width ||
+ source_fmt.format.height != q->format.height) {
+ dev_err(&cio2->pci_dev->dev,
+ "Wrong width or height %ux%u (%ux%u expected)\n",
+ q->format.width, q->format.height,
+ source_fmt.format.width, source_fmt.format.height);
+ return -EINVAL;
+ }
+
+ if (!cio2_find_format(&q->format.pixelformat, &source_fmt.format.code))
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops cio2_subdev_core_ops = {
+ .subscribe_event = cio2_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_internal_ops cio2_subdev_internal_ops = {
+ .open = cio2_subdev_open,
+};
+
+static const struct v4l2_subdev_pad_ops cio2_subdev_pad_ops = {
+ .link_validate = v4l2_subdev_link_validate_default,
+ .get_fmt = cio2_subdev_get_fmt,
+ .set_fmt = cio2_subdev_set_fmt,
+ .enum_mbus_code = cio2_subdev_enum_mbus_code,
+};
+
+static const struct v4l2_subdev_ops cio2_subdev_ops = {
+ .core = &cio2_subdev_core_ops,
+ .pad = &cio2_subdev_pad_ops,
+};
+
+/******* V4L2 sub-device asynchronous registration callbacks***********/
+
+struct sensor_async_subdev {
+ struct v4l2_async_subdev asd;
+ struct csi2_bus_info csi2;
+};
+
+/* The .bound() notifier callback when a match is found */
+static int cio2_notifier_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ struct cio2_device *cio2 = container_of(notifier,
+ struct cio2_device, notifier);
+ struct sensor_async_subdev *s_asd = container_of(asd,
+ struct sensor_async_subdev, asd);
+ struct cio2_queue *q;
+
+ if (cio2->queue[s_asd->csi2.port].sensor)
+ return -EBUSY;
+
+ q = &cio2->queue[s_asd->csi2.port];
+
+ q->csi2 = s_asd->csi2;
+ q->sensor = sd;
+ q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port);
+
+ return 0;
+}
+
+/* The .unbind callback */
+static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ struct cio2_device *cio2 = container_of(notifier,
+ struct cio2_device, notifier);
+ struct sensor_async_subdev *s_asd = container_of(asd,
+ struct sensor_async_subdev, asd);
+
+ cio2->queue[s_asd->csi2.port].sensor = NULL;
+}
+
+/* .complete() is called after all subdevices have been located */
+static int cio2_notifier_complete(struct v4l2_async_notifier *notifier)
+{
+ struct cio2_device *cio2 = container_of(notifier, struct cio2_device,
+ notifier);
+ struct sensor_async_subdev *s_asd;
+ struct cio2_queue *q;
+ unsigned int i, pad;
+ int ret;
+
+ for (i = 0; i < notifier->num_subdevs; i++) {
+ s_asd = container_of(cio2->notifier.subdevs[i],
+ struct sensor_async_subdev, asd);
+ q = &cio2->queue[s_asd->csi2.port];
+
+ for (pad = 0; pad < q->sensor->entity.num_pads; pad++)
+ if (q->sensor->entity.pads[pad].flags &
+ MEDIA_PAD_FL_SOURCE)
+ break;
+
+ if (pad == q->sensor->entity.num_pads) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed to find src pad for %s\n",
+ q->sensor->name);
+ return -ENXIO;
+ }
+
+ ret = media_create_pad_link(
+ &q->sensor->entity, pad,
+ &q->subdev.entity, CIO2_PAD_SINK,
+ 0);
+ if (ret) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed to create link for %s\n",
+ cio2->queue[i].sensor->name);
+ return ret;
+ }
+ }
+
+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev);
+}
+
+static const struct v4l2_async_notifier_operations cio2_async_ops = {
+ .bound = cio2_notifier_bound,
+ .unbind = cio2_notifier_unbind,
+ .complete = cio2_notifier_complete,
+};
+
+static int cio2_fwnode_parse(struct device *dev,
+ struct v4l2_fwnode_endpoint *vep,
+ struct v4l2_async_subdev *asd)
+{
+ struct sensor_async_subdev *s_asd =
+ container_of(asd, struct sensor_async_subdev, asd);
+
+ if (vep->bus_type != V4L2_MBUS_CSI2) {
+ dev_err(dev, "Only CSI2 bus type is currently supported\n");
+ return -EINVAL;
+ }
+
+ s_asd->csi2.port = vep->base.port;
+ s_asd->csi2.lanes = vep->bus.mipi_csi2.num_data_lanes;
+
+ return 0;
+}
+
+static int cio2_notifier_init(struct cio2_device *cio2)
+{
+ int ret;
+
+ ret = v4l2_async_notifier_parse_fwnode_endpoints(
+ &cio2->pci_dev->dev, &cio2->notifier,
+ sizeof(struct sensor_async_subdev),
+ cio2_fwnode_parse);
+ if (ret < 0)
+ return ret;
+
+ if (!cio2->notifier.num_subdevs)
+ return -ENODEV; /* no endpoint */
+
+ cio2->notifier.ops = &cio2_async_ops;
+ ret = v4l2_async_notifier_register(&cio2->v4l2_dev, &cio2->notifier);
+ if (ret) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed to register async notifier : %d\n", ret);
+ v4l2_async_notifier_cleanup(&cio2->notifier);
+ }
+
+ return ret;
+}
+
+static void cio2_notifier_exit(struct cio2_device *cio2)
+{
+ v4l2_async_notifier_unregister(&cio2->notifier);
+ v4l2_async_notifier_cleanup(&cio2->notifier);
+}
+
+/**************** Queue initialization ****************/
+static const struct media_entity_operations cio2_media_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct media_entity_operations cio2_video_entity_ops = {
+ .link_validate = cio2_video_link_validate,
+};
+
+static int cio2_queue_init(struct cio2_device *cio2, struct cio2_queue *q)
+{
+ static const u32 default_width = 1936;
+ static const u32 default_height = 1096;
+ const struct ipu3_cio2_fmt dflt_fmt = formats[0];
+
+ struct video_device *vdev = &q->vdev;
+ struct vb2_queue *vbq = &q->vbq;
+ struct v4l2_subdev *subdev = &q->subdev;
+ struct v4l2_mbus_framefmt *fmt;
+ int r;
+
+ /* Initialize miscellaneous variables */
+ mutex_init(&q->lock);
+
+ /* Initialize formats to default values */
+ fmt = &q->subdev_fmt;
+ fmt->width = default_width;
+ fmt->height = default_height;
+ fmt->code = dflt_fmt.mbus_code;
+ fmt->field = V4L2_FIELD_NONE;
+
+ q->format.width = default_width;
+ q->format.height = default_height;
+ q->format.pixelformat = dflt_fmt.fourcc;
+ q->format.colorspace = V4L2_COLORSPACE_RAW;
+ q->format.field = V4L2_FIELD_NONE;
+ q->format.num_planes = 1;
+ q->format.plane_fmt[0].bytesperline =
+ cio2_bytesperline(q->format.width);
+ q->format.plane_fmt[0].sizeimage = q->format.plane_fmt[0].bytesperline *
+ q->format.height;
+
+ /* Initialize fbpt */
+ r = cio2_fbpt_init(cio2, q);
+ if (r)
+ goto fail_fbpt;
+
+ /* Initialize media entities */
+ q->subdev_pads[CIO2_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
+ MEDIA_PAD_FL_MUST_CONNECT;
+ q->subdev_pads[CIO2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ subdev->entity.ops = &cio2_media_ops;
+ subdev->internal_ops = &cio2_subdev_internal_ops;
+ r = media_entity_pads_init(&subdev->entity, CIO2_PADS, q->subdev_pads);
+ if (r) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed initialize subdev media entity (%d)\n", r);
+ goto fail_subdev_media_entity;
+ }
+
+ q->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+ vdev->entity.ops = &cio2_video_entity_ops;
+ r = media_entity_pads_init(&vdev->entity, 1, &q->vdev_pad);
+ if (r) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed initialize videodev media entity (%d)\n", r);
+ goto fail_vdev_media_entity;
+ }
+
+ /* Initialize subdev */
+ v4l2_subdev_init(subdev, &cio2_subdev_ops);
+ subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
+ subdev->owner = THIS_MODULE;
+ snprintf(subdev->name, sizeof(subdev->name),
+ CIO2_ENTITY_NAME ":%td", q - cio2->queue);
+ v4l2_set_subdevdata(subdev, cio2);
+ r = v4l2_device_register_subdev(&cio2->v4l2_dev, subdev);
+ if (r) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed initialize subdev (%d)\n", r);
+ goto fail_subdev;
+ }
+
+ /* Initialize vbq */
+ vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ vbq->io_modes = VB2_USERPTR | VB2_MMAP | VB2_DMABUF;
+ vbq->ops = &cio2_vb2_ops;
+ vbq->mem_ops = &vb2_dma_sg_memops;
+ vbq->buf_struct_size = sizeof(struct cio2_buffer);
+ vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vbq->min_buffers_needed = 1;
+ vbq->drv_priv = cio2;
+ vbq->lock = &q->lock;
+ r = vb2_queue_init(vbq);
+ if (r) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed to initialize videobuf2 queue (%d)\n", r);
+ goto fail_vbq;
+ }
+
+ /* Initialize vdev */
+ snprintf(vdev->name, sizeof(vdev->name),
+ "%s:%td", CIO2_NAME, q - cio2->queue);
+ vdev->release = video_device_release_empty;
+ vdev->fops = &cio2_v4l2_fops;
+ vdev->ioctl_ops = &cio2_v4l2_ioctl_ops;
+ vdev->lock = &cio2->lock;
+ vdev->v4l2_dev = &cio2->v4l2_dev;
+ vdev->queue = &q->vbq;
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
+ video_set_drvdata(vdev, cio2);
+ r = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (r) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed to register video device (%d)\n", r);
+ goto fail_vdev;
+ }
+
+ /* Create link from CIO2 subdev to output node */
+ r = media_create_pad_link(
+ &subdev->entity, CIO2_PAD_SOURCE, &vdev->entity, 0,
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+ if (r)
+ goto fail_link;
+
+ return 0;
+
+fail_link:
+ video_unregister_device(&q->vdev);
+fail_vdev:
+ vb2_queue_release(vbq);
+fail_vbq:
+ v4l2_device_unregister_subdev(subdev);
+fail_subdev:
+ media_entity_cleanup(&vdev->entity);
+fail_vdev_media_entity:
+ media_entity_cleanup(&subdev->entity);
+fail_subdev_media_entity:
+ cio2_fbpt_exit(q, &cio2->pci_dev->dev);
+fail_fbpt:
+ mutex_destroy(&q->lock);
+
+ return r;
+}
+
+static void cio2_queue_exit(struct cio2_device *cio2, struct cio2_queue *q)
+{
+ video_unregister_device(&q->vdev);
+ media_entity_cleanup(&q->vdev.entity);
+ vb2_queue_release(&q->vbq);
+ v4l2_device_unregister_subdev(&q->subdev);
+ media_entity_cleanup(&q->subdev.entity);
+ cio2_fbpt_exit(q, &cio2->pci_dev->dev);
+ mutex_destroy(&q->lock);
+}
+
+static int cio2_queues_init(struct cio2_device *cio2)
+{
+ int i, r;
+
+ for (i = 0; i < CIO2_QUEUES; i++) {
+ r = cio2_queue_init(cio2, &cio2->queue[i]);
+ if (r)
+ break;
+ }
+
+ if (i == CIO2_QUEUES)
+ return 0;
+
+ for (i--; i >= 0; i--)
+ cio2_queue_exit(cio2, &cio2->queue[i]);
+
+ return r;
+}
+
+static void cio2_queues_exit(struct cio2_device *cio2)
+{
+ unsigned int i;
+
+ for (i = 0; i < CIO2_QUEUES; i++)
+ cio2_queue_exit(cio2, &cio2->queue[i]);
+}
+
+/**************** PCI interface ****************/
+
+static int cio2_pci_config_setup(struct pci_dev *dev)
+{
+ u16 pci_command;
+ int r = pci_enable_msi(dev);
+
+ if (r) {
+ dev_err(&dev->dev, "failed to enable MSI (%d)\n", r);
+ return r;
+ }
+
+ pci_read_config_word(dev, PCI_COMMAND, &pci_command);
+ pci_command |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
+ PCI_COMMAND_INTX_DISABLE;
+ pci_write_config_word(dev, PCI_COMMAND, pci_command);
+
+ return 0;
+}
+
+static int cio2_pci_probe(struct pci_dev *pci_dev,
+ const struct pci_device_id *id)
+{
+ struct cio2_device *cio2;
+ phys_addr_t phys;
+ void __iomem *const *iomap;
+ int r;
+
+ cio2 = devm_kzalloc(&pci_dev->dev, sizeof(*cio2), GFP_KERNEL);
+ if (!cio2)
+ return -ENOMEM;
+ cio2->pci_dev = pci_dev;
+
+ r = pcim_enable_device(pci_dev);
+ if (r) {
+ dev_err(&pci_dev->dev, "failed to enable device (%d)\n", r);
+ return r;
+ }
+
+ dev_info(&pci_dev->dev, "device 0x%x (rev: 0x%x)\n",
+ pci_dev->device, pci_dev->revision);
+
+ phys = pci_resource_start(pci_dev, CIO2_PCI_BAR);
+
+ r = pcim_iomap_regions(pci_dev, 1 << CIO2_PCI_BAR, pci_name(pci_dev));
+ if (r) {
+ dev_err(&pci_dev->dev, "failed to remap I/O memory (%d)\n", r);
+ return -ENODEV;
+ }
+
+ iomap = pcim_iomap_table(pci_dev);
+ if (!iomap) {
+ dev_err(&pci_dev->dev, "failed to iomap table\n");
+ return -ENODEV;
+ }
+
+ cio2->base = iomap[CIO2_PCI_BAR];
+
+ pci_set_drvdata(pci_dev, cio2);
+
+ pci_set_master(pci_dev);
+
+ r = pci_set_dma_mask(pci_dev, CIO2_DMA_MASK);
+ if (r) {
+ dev_err(&pci_dev->dev, "failed to set DMA mask (%d)\n", r);
+ return -ENODEV;
+ }
+
+ r = cio2_pci_config_setup(pci_dev);
+ if (r)
+ return -ENODEV;
+
+ r = cio2_fbpt_init_dummy(cio2);
+ if (r)
+ return r;
+
+ mutex_init(&cio2->lock);
+
+ cio2->media_dev.dev = &cio2->pci_dev->dev;
+ strlcpy(cio2->media_dev.model, CIO2_DEVICE_NAME,
+ sizeof(cio2->media_dev.model));
+ snprintf(cio2->media_dev.bus_info, sizeof(cio2->media_dev.bus_info),
+ "PCI:%s", pci_name(cio2->pci_dev));
+ cio2->media_dev.hw_revision = 0;
+
+ media_device_init(&cio2->media_dev);
+ r = media_device_register(&cio2->media_dev);
+ if (r < 0)
+ goto fail_mutex_destroy;
+
+ cio2->v4l2_dev.mdev = &cio2->media_dev;
+ r = v4l2_device_register(&pci_dev->dev, &cio2->v4l2_dev);
+ if (r) {
+ dev_err(&pci_dev->dev,
+ "failed to register V4L2 device (%d)\n", r);
+ goto fail_media_device_unregister;
+ }
+
+ r = cio2_queues_init(cio2);
+ if (r)
+ goto fail_v4l2_device_unregister;
+
+ /* Register notifier for subdevices we care */
+ r = cio2_notifier_init(cio2);
+ if (r)
+ goto fail_cio2_queue_exit;
+
+ r = devm_request_irq(&pci_dev->dev, pci_dev->irq, cio2_irq,
+ IRQF_SHARED, CIO2_NAME, cio2);
+ if (r) {
+ dev_err(&pci_dev->dev, "failed to request IRQ (%d)\n", r);
+ goto fail;
+ }
+
+ pm_runtime_put_noidle(&pci_dev->dev);
+ pm_runtime_allow(&pci_dev->dev);
+
+ return 0;
+
+fail:
+ cio2_notifier_exit(cio2);
+fail_cio2_queue_exit:
+ cio2_queues_exit(cio2);
+fail_v4l2_device_unregister:
+ v4l2_device_unregister(&cio2->v4l2_dev);
+fail_media_device_unregister:
+ media_device_unregister(&cio2->media_dev);
+ media_device_cleanup(&cio2->media_dev);
+fail_mutex_destroy:
+ mutex_destroy(&cio2->lock);
+ cio2_fbpt_exit_dummy(cio2);
+
+ return r;
+}
+
+static void cio2_pci_remove(struct pci_dev *pci_dev)
+{
+ struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
+ unsigned int i;
+
+ cio2_notifier_exit(cio2);
+ cio2_fbpt_exit_dummy(cio2);
+ for (i = 0; i < CIO2_QUEUES; i++)
+ cio2_queue_exit(cio2, &cio2->queue[i]);
+ v4l2_device_unregister(&cio2->v4l2_dev);
+ media_device_unregister(&cio2->media_dev);
+ media_device_cleanup(&cio2->media_dev);
+ mutex_destroy(&cio2->lock);
+}
+
+static int cio2_runtime_suspend(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
+ void __iomem *const base = cio2->base;
+ u16 pm;
+
+ writel(CIO2_D0I3C_I3, base + CIO2_REG_D0I3C);
+ dev_dbg(dev, "cio2 runtime suspend.\n");
+
+ pci_read_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, &pm);
+ pm = (pm >> CIO2_PMCSR_D0D3_SHIFT) << CIO2_PMCSR_D0D3_SHIFT;
+ pm |= CIO2_PMCSR_D3;
+ pci_write_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, pm);
+
+ return 0;
+}
+
+static int cio2_runtime_resume(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
+ void __iomem *const base = cio2->base;
+ u16 pm;
+
+ writel(CIO2_D0I3C_RR, base + CIO2_REG_D0I3C);
+ dev_dbg(dev, "cio2 runtime resume.\n");
+
+ pci_read_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, &pm);
+ pm = (pm >> CIO2_PMCSR_D0D3_SHIFT) << CIO2_PMCSR_D0D3_SHIFT;
+ pci_write_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, pm);
+
+ return 0;
+}
+
+static int cio2_suspend(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
+ struct cio2_queue *q = cio2->cur_queue;
+
+ dev_dbg(dev, "cio2 suspend\n");
+ if (!cio2->streaming)
+ return 0;
+
+ /* Stop stream */
+ cio2_hw_exit(cio2, q);
+
+ pm_runtime_force_suspend(dev);
+
+ /*
+ * Upon resume, hw starts to process the fbpt entries from beginning,
+ * so relocate the queued buffs to the fbpt head before suspend.
+ */
+ cio2_fbpt_rearrange(cio2, q);
+ q->bufs_first = 0;
+ q->bufs_next = 0;
+
+ return 0;
+}
+
+static int cio2_resume(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
+ int r = 0;
+ struct cio2_queue *q = cio2->cur_queue;
+
+ dev_dbg(dev, "cio2 resume\n");
+ if (!cio2->streaming)
+ return 0;
+ /* Start stream */
+ r = pm_runtime_force_resume(&cio2->pci_dev->dev);
+ if (r < 0) {
+ dev_err(&cio2->pci_dev->dev,
+ "failed to set power %d\n", r);
+ return r;
+ }
+
+ r = cio2_hw_init(cio2, q);
+ if (r)
+ dev_err(dev, "fail to init cio2 hw\n");
+
+ return r;
+}
+
+static const struct dev_pm_ops cio2_pm_ops = {
+ SET_RUNTIME_PM_OPS(&cio2_runtime_suspend, &cio2_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(&cio2_suspend, &cio2_resume)
+};
+
+static const struct pci_device_id cio2_pci_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, CIO2_PCI_ID) },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, cio2_pci_id_table);
+
+static struct pci_driver cio2_pci_driver = {
+ .name = CIO2_NAME,
+ .id_table = cio2_pci_id_table,
+ .probe = cio2_pci_probe,
+ .remove = cio2_pci_remove,
+ .driver = {
+ .pm = &cio2_pm_ops,
+ },
+};
+
+module_pci_driver(cio2_pci_driver);
+
+MODULE_AUTHOR("Tuukka Toivonen <tuukka.toivonen@intel.com>");
+MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
+MODULE_AUTHOR("Jian Xu Zheng <jian.xu.zheng@intel.com>");
+MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>");
+MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("IPU3 CIO2 driver");
diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h
new file mode 100644
index 000000000000..1a559990920f
--- /dev/null
+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2017 Intel Corporation.
+ *
+ * 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 __IPU3_CIO2_H
+#define __IPU3_CIO2_H
+
+#define CIO2_NAME "ipu3-cio2"
+#define CIO2_DEVICE_NAME "Intel IPU3 CIO2"
+#define CIO2_ENTITY_NAME "ipu3-csi2"
+#define CIO2_PCI_ID 0x9d32
+#define CIO2_PCI_BAR 0
+#define CIO2_DMA_MASK DMA_BIT_MASK(39)
+#define CIO2_IMAGE_MAX_WIDTH 4224
+#define CIO2_IMAGE_MAX_LENGTH 3136
+
+#define CIO2_IMAGE_MAX_WIDTH 4224
+#define CIO2_IMAGE_MAX_LENGTH 3136
+
+/* 32MB = 8xFBPT_entry */
+#define CIO2_MAX_LOPS 8
+#define CIO2_MAX_BUFFERS (PAGE_SIZE / 16 / CIO2_MAX_LOPS)
+
+#define CIO2_PAD_SINK 0
+#define CIO2_PAD_SOURCE 1
+#define CIO2_PADS 2
+
+#define CIO2_NUM_DMA_CHAN 20
+#define CIO2_NUM_PORTS 4 /* DPHYs */
+
+/* 1 for each sensor */
+#define CIO2_QUEUES CIO2_NUM_PORTS
+
+/* Register and bit field definitions */
+#define CIO2_REG_PIPE_BASE(n) ((n) * 0x0400) /* n = 0..3 */
+#define CIO2_REG_CSIRX_BASE 0x000
+#define CIO2_REG_MIPIBE_BASE 0x100
+#define CIO2_REG_PIXELGEN_BAS 0x200
+#define CIO2_REG_IRQCTRL_BASE 0x300
+#define CIO2_REG_GPREG_BASE 0x1000
+
+/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_CSIRX_BASE */
+#define CIO2_REG_CSIRX_ENABLE (CIO2_REG_CSIRX_BASE + 0x0)
+#define CIO2_REG_CSIRX_NOF_ENABLED_LANES (CIO2_REG_CSIRX_BASE + 0x4)
+#define CIO2_REG_CSIRX_SP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x10)
+#define CIO2_REG_CSIRX_LP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x14)
+#define CIO2_CSIRX_IF_CONFIG_FILTEROUT 0x00
+#define CIO2_CSIRX_IF_CONFIG_FILTEROUT_VC_INACTIVE 0x01
+#define CIO2_CSIRX_IF_CONFIG_PASS 0x02
+#define CIO2_CSIRX_IF_CONFIG_FLAG_ERROR BIT(2)
+#define CIO2_REG_CSIRX_STATUS (CIO2_REG_CSIRX_BASE + 0x18)
+#define CIO2_REG_CSIRX_STATUS_DLANE_HS (CIO2_REG_CSIRX_BASE + 0x1c)
+#define CIO2_CSIRX_STATUS_DLANE_HS_MASK 0xff
+#define CIO2_REG_CSIRX_STATUS_DLANE_LP (CIO2_REG_CSIRX_BASE + 0x20)
+#define CIO2_CSIRX_STATUS_DLANE_LP_MASK 0xffffff
+/* Termination enable and settle in 0.0625ns units, lane=0..3 or -1 for clock */
+#define CIO2_REG_CSIRX_DLY_CNT_TERMEN(lane) \
+ (CIO2_REG_CSIRX_BASE + 0x2c + 8 * (lane))
+#define CIO2_REG_CSIRX_DLY_CNT_SETTLE(lane) \
+ (CIO2_REG_CSIRX_BASE + 0x30 + 8 * (lane))
+/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_MIPIBE_BASE */
+#define CIO2_REG_MIPIBE_ENABLE (CIO2_REG_MIPIBE_BASE + 0x0)
+#define CIO2_REG_MIPIBE_STATUS (CIO2_REG_MIPIBE_BASE + 0x4)
+#define CIO2_REG_MIPIBE_COMP_FORMAT(vc) \
+ (CIO2_REG_MIPIBE_BASE + 0x8 + 0x4 * (vc))
+#define CIO2_REG_MIPIBE_FORCE_RAW8 (CIO2_REG_MIPIBE_BASE + 0x20)
+#define CIO2_REG_MIPIBE_FORCE_RAW8_ENABLE BIT(0)
+#define CIO2_REG_MIPIBE_FORCE_RAW8_USE_TYPEID BIT(1)
+#define CIO2_REG_MIPIBE_FORCE_RAW8_TYPEID_SHIFT 2
+
+#define CIO2_REG_MIPIBE_IRQ_STATUS (CIO2_REG_MIPIBE_BASE + 0x24)
+#define CIO2_REG_MIPIBE_IRQ_CLEAR (CIO2_REG_MIPIBE_BASE + 0x28)
+#define CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD (CIO2_REG_MIPIBE_BASE + 0x68)
+#define CIO2_MIPIBE_GLOBAL_LUT_DISREGARD 1
+#define CIO2_REG_MIPIBE_PKT_STALL_STATUS (CIO2_REG_MIPIBE_BASE + 0x6c)
+#define CIO2_REG_MIPIBE_PARSE_GSP_THROUGH_LP_LUT_REG_IDX \
+ (CIO2_REG_MIPIBE_BASE + 0x70)
+#define CIO2_REG_MIPIBE_SP_LUT_ENTRY(vc) \
+ (CIO2_REG_MIPIBE_BASE + 0x74 + 4 * (vc))
+#define CIO2_REG_MIPIBE_LP_LUT_ENTRY(m) /* m = 0..15 */ \
+ (CIO2_REG_MIPIBE_BASE + 0x84 + 4 * (m))
+#define CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD 1
+#define CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT 1
+#define CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT 5
+#define CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT 7
+
+/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_IRQCTRL_BASE */
+/* IRQ registers are 18-bit wide, see cio2_irq_error for bit definitions */
+#define CIO2_REG_IRQCTRL_EDGE (CIO2_REG_IRQCTRL_BASE + 0x00)
+#define CIO2_REG_IRQCTRL_MASK (CIO2_REG_IRQCTRL_BASE + 0x04)
+#define CIO2_REG_IRQCTRL_STATUS (CIO2_REG_IRQCTRL_BASE + 0x08)
+#define CIO2_REG_IRQCTRL_CLEAR (CIO2_REG_IRQCTRL_BASE + 0x0c)
+#define CIO2_REG_IRQCTRL_ENABLE (CIO2_REG_IRQCTRL_BASE + 0x10)
+#define CIO2_REG_IRQCTRL_LEVEL_NOT_PULSE (CIO2_REG_IRQCTRL_BASE + 0x14)
+
+#define CIO2_REG_GPREG_SRST (CIO2_REG_GPREG_BASE + 0x0)
+#define CIO2_GPREG_SRST_ALL 0xffff /* Reset all */
+#define CIO2_REG_FB_HPLL_FREQ (CIO2_REG_GPREG_BASE + 0x08)
+#define CIO2_REG_ISCLK_RATIO (CIO2_REG_GPREG_BASE + 0xc)
+
+#define CIO2_REG_CGC 0x1400
+#define CIO2_CGC_CSI2_TGE BIT(0)
+#define CIO2_CGC_PRIM_TGE BIT(1)
+#define CIO2_CGC_SIDE_TGE BIT(2)
+#define CIO2_CGC_XOSC_TGE BIT(3)
+#define CIO2_CGC_MPLL_SHUTDOWN_EN BIT(4)
+#define CIO2_CGC_D3I3_TGE BIT(5)
+#define CIO2_CGC_CSI2_INTERFRAME_TGE BIT(6)
+#define CIO2_CGC_CSI2_PORT_DCGE BIT(8)
+#define CIO2_CGC_CSI2_DCGE BIT(9)
+#define CIO2_CGC_SIDE_DCGE BIT(10)
+#define CIO2_CGC_PRIM_DCGE BIT(11)
+#define CIO2_CGC_ROSC_DCGE BIT(12)
+#define CIO2_CGC_XOSC_DCGE BIT(13)
+#define CIO2_CGC_FLIS_DCGE BIT(14)
+#define CIO2_CGC_CLKGATE_HOLDOFF_SHIFT 20
+#define CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT 24
+#define CIO2_REG_D0I3C 0x1408
+#define CIO2_D0I3C_I3 BIT(2) /* Set D0I3 */
+#define CIO2_D0I3C_RR BIT(3) /* Restore? */
+#define CIO2_REG_SWRESET 0x140c
+#define CIO2_SWRESET_SWRESET 1
+#define CIO2_REG_SENSOR_ACTIVE 0x1410
+#define CIO2_REG_INT_STS 0x1414
+#define CIO2_REG_INT_STS_EXT_OE 0x1418
+#define CIO2_INT_EXT_OE_DMAOE_SHIFT 0
+#define CIO2_INT_EXT_OE_DMAOE_MASK 0x7ffff
+#define CIO2_INT_EXT_OE_OES_SHIFT 24
+#define CIO2_INT_EXT_OE_OES_MASK (0xf << CIO2_INT_EXT_OE_OES_SHIFT)
+#define CIO2_REG_INT_EN 0x1420
+#define CIO2_REG_INT_EN_IRQ (1 << 24)
+#define CIO2_REG_INT_EN_IOS(dma) (1 << (((dma) >> 1) + 12))
+/*
+ * Interrupt on completion bit, Eg. DMA 0-3 maps to bit 0-3,
+ * DMA4 & DMA5 map to bit 4 ... DMA18 & DMA19 map to bit 11 Et cetera
+ */
+#define CIO2_INT_IOC(dma) (1 << ((dma) < 4 ? (dma) : ((dma) >> 1) + 2))
+#define CIO2_INT_IOC_SHIFT 0
+#define CIO2_INT_IOC_MASK (0x7ff << CIO2_INT_IOC_SHIFT)
+#define CIO2_INT_IOS_IOLN(dma) (1 << (((dma) >> 1) + 12))
+#define CIO2_INT_IOS_IOLN_SHIFT 12
+#define CIO2_INT_IOS_IOLN_MASK (0x3ff << CIO2_INT_IOS_IOLN_SHIFT)
+#define CIO2_INT_IOIE BIT(22)
+#define CIO2_INT_IOOE BIT(23)
+#define CIO2_INT_IOIRQ BIT(24)
+#define CIO2_REG_INT_EN_EXT_OE 0x1424
+#define CIO2_REG_DMA_DBG 0x1448
+#define CIO2_REG_DMA_DBG_DMA_INDEX_SHIFT 0
+#define CIO2_REG_PBM_ARB_CTRL 0x1460
+#define CIO2_PBM_ARB_CTRL_LANES_DIV 0 /* 4-4-2-2 lanes */
+#define CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT 0
+#define CIO2_PBM_ARB_CTRL_LE_EN BIT(7)
+#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN 2
+#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT 8
+#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP 480
+#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT 16
+#define CIO2_REG_PBM_WMCTRL1 0x1464
+#define CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT 0
+#define CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT 8
+#define CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT 16
+#define CIO2_PBM_WMCTRL1_TS_COUNT_DISABLE BIT(31)
+#define CIO2_PBM_WMCTRL1_MIN_2CK (4 << CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT)
+#define CIO2_PBM_WMCTRL1_MID1_2CK (16 << CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT)
+#define CIO2_PBM_WMCTRL1_MID2_2CK (21 << CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT)
+#define CIO2_REG_PBM_WMCTRL2 0x1468
+#define CIO2_PBM_WMCTRL2_HWM_2CK 40
+#define CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT 0
+#define CIO2_PBM_WMCTRL2_LWM_2CK 22
+#define CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT 8
+#define CIO2_PBM_WMCTRL2_OBFFWM_2CK 2
+#define CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT 16
+#define CIO2_PBM_WMCTRL2_TRANSDYN 1
+#define CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT 24
+#define CIO2_PBM_WMCTRL2_DYNWMEN BIT(28)
+#define CIO2_PBM_WMCTRL2_OBFF_MEM_EN BIT(29)
+#define CIO2_PBM_WMCTRL2_OBFF_CPU_EN BIT(30)
+#define CIO2_PBM_WMCTRL2_DRAINNOW BIT(31)
+#define CIO2_REG_PBM_TS_COUNT 0x146c
+#define CIO2_REG_PBM_FOPN_ABORT 0x1474
+/* below n = 0..3 */
+#define CIO2_PBM_FOPN_ABORT(n) (0x1 << 8 * (n))
+#define CIO2_PBM_FOPN_FORCE_ABORT(n) (0x2 << 8 * (n))
+#define CIO2_PBM_FOPN_FRAMEOPEN(n) (0x8 << 8 * (n))
+#define CIO2_REG_LTRCTRL 0x1480
+#define CIO2_LTRCTRL_LTRDYNEN BIT(16)
+#define CIO2_LTRCTRL_LTRSTABLETIME_SHIFT 8
+#define CIO2_LTRCTRL_LTRSTABLETIME_MASK 0xff
+#define CIO2_LTRCTRL_LTRSEL1S3 BIT(7)
+#define CIO2_LTRCTRL_LTRSEL1S2 BIT(6)
+#define CIO2_LTRCTRL_LTRSEL1S1 BIT(5)
+#define CIO2_LTRCTRL_LTRSEL1S0 BIT(4)
+#define CIO2_LTRCTRL_LTRSEL2S3 BIT(3)
+#define CIO2_LTRCTRL_LTRSEL2S2 BIT(2)
+#define CIO2_LTRCTRL_LTRSEL2S1 BIT(1)
+#define CIO2_LTRCTRL_LTRSEL2S0 BIT(0)
+#define CIO2_REG_LTRVAL23 0x1484
+#define CIO2_REG_LTRVAL01 0x1488
+#define CIO2_LTRVAL02_VAL_SHIFT 0
+#define CIO2_LTRVAL02_SCALE_SHIFT 10
+#define CIO2_LTRVAL13_VAL_SHIFT 16
+#define CIO2_LTRVAL13_SCALE_SHIFT 26
+
+#define CIO2_LTRVAL0_VAL 175
+/* Value times 1024 ns */
+#define CIO2_LTRVAL0_SCALE 2
+#define CIO2_LTRVAL1_VAL 90
+#define CIO2_LTRVAL1_SCALE 2
+#define CIO2_LTRVAL2_VAL 90
+#define CIO2_LTRVAL2_SCALE 2
+#define CIO2_LTRVAL3_VAL 90
+#define CIO2_LTRVAL3_SCALE 2
+
+#define CIO2_REG_CDMABA(n) (0x1500 + 0x10 * (n)) /* n = 0..19 */
+#define CIO2_REG_CDMARI(n) (0x1504 + 0x10 * (n))
+#define CIO2_CDMARI_FBPT_RP_SHIFT 0
+#define CIO2_CDMARI_FBPT_RP_MASK 0xff
+#define CIO2_REG_CDMAC0(n) (0x1508 + 0x10 * (n))
+#define CIO2_CDMAC0_FBPT_LEN_SHIFT 0
+#define CIO2_CDMAC0_FBPT_WIDTH_SHIFT 8
+#define CIO2_CDMAC0_FBPT_NS BIT(25)
+#define CIO2_CDMAC0_DMA_INTR_ON_FS BIT(26)
+#define CIO2_CDMAC0_DMA_INTR_ON_FE BIT(27)
+#define CIO2_CDMAC0_FBPT_UPDATE_FIFO_FULL BIT(28)
+#define CIO2_CDMAC0_FBPT_FIFO_FULL_FIX_DIS BIT(29)
+#define CIO2_CDMAC0_DMA_EN BIT(30)
+#define CIO2_CDMAC0_DMA_HALTED BIT(31)
+#define CIO2_REG_CDMAC1(n) (0x150c + 0x10 * (n))
+#define CIO2_CDMAC1_LINENUMINT_SHIFT 0
+#define CIO2_CDMAC1_LINENUMUPDATE_SHIFT 16
+/* n = 0..3 */
+#define CIO2_REG_PXM_PXF_FMT_CFG0(n) (0x1700 + 0x30 * (n))
+#define CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT 0
+#define CIO2_PXM_PXF_FMT_CFG_SID1_SHIFT 16
+#define CIO2_PXM_PXF_FMT_CFG_PCK_64B (0 << 0)
+#define CIO2_PXM_PXF_FMT_CFG_PCK_32B (1 << 0)
+#define CIO2_PXM_PXF_FMT_CFG_BPP_08 (0 << 2)
+#define CIO2_PXM_PXF_FMT_CFG_BPP_10 (1 << 2)
+#define CIO2_PXM_PXF_FMT_CFG_BPP_12 (2 << 2)
+#define CIO2_PXM_PXF_FMT_CFG_BPP_14 (3 << 2)
+#define CIO2_PXM_PXF_FMT_CFG_SPEC_4PPC (0 << 4)
+#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_RGBA (1 << 4)
+#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_ARGB (2 << 4)
+#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR2 (3 << 4)
+#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR3 (4 << 4)
+#define CIO2_PXM_PXF_FMT_CFG_SPEC_NV16 (5 << 4)
+#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_AB (1 << 7)
+#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_CD (1 << 8)
+#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_AC (1 << 9)
+#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_BD (1 << 10)
+#define CIO2_REG_INT_STS_EXT_IE 0x17e4
+#define CIO2_REG_INT_EN_EXT_IE 0x17e8
+#define CIO2_INT_EXT_IE_ECC_RE(n) (0x01 << (8 * (n)))
+#define CIO2_INT_EXT_IE_DPHY_NR(n) (0x02 << (8 * (n)))
+#define CIO2_INT_EXT_IE_ECC_NR(n) (0x04 << (8 * (n)))
+#define CIO2_INT_EXT_IE_CRCERR(n) (0x08 << (8 * (n)))
+#define CIO2_INT_EXT_IE_INTERFRAMEDATA(n) (0x10 << (8 * (n)))
+#define CIO2_INT_EXT_IE_PKT2SHORT(n) (0x20 << (8 * (n)))
+#define CIO2_INT_EXT_IE_PKT2LONG(n) (0x40 << (8 * (n)))
+#define CIO2_INT_EXT_IE_IRQ(n) (0x80 << (8 * (n)))
+#define CIO2_REG_PXM_FRF_CFG(n) (0x1720 + 0x30 * (n))
+#define CIO2_PXM_FRF_CFG_FNSEL BIT(0)
+#define CIO2_PXM_FRF_CFG_FN_RST BIT(1)
+#define CIO2_PXM_FRF_CFG_ABORT BIT(2)
+#define CIO2_PXM_FRF_CFG_CRC_TH_SHIFT 3
+#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR BIT(8)
+#define CIO2_PXM_FRF_CFG_MSK_ECC_RE BIT(9)
+#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE BIT(10)
+#define CIO2_PXM_FRF_CFG_EVEN_ODD_MODE_SHIFT 11
+#define CIO2_PXM_FRF_CFG_MASK_CRC_THRES BIT(13)
+#define CIO2_PXM_FRF_CFG_MASK_CSI_ACCEPT BIT(14)
+#define CIO2_PXM_FRF_CFG_CIOHC_FS_MODE BIT(15)
+#define CIO2_PXM_FRF_CFG_CIOHC_FRST_FRM_SHIFT 16
+#define CIO2_REG_PXM_SID2BID0(n) (0x1724 + 0x30 * (n))
+#define CIO2_FB_HPLL_FREQ 0x2
+#define CIO2_ISCLK_RATIO 0xc
+
+#define CIO2_IRQCTRL_MASK 0x3ffff
+
+#define CIO2_INT_EN_EXT_OE_MASK 0x8f0fffff
+
+#define CIO2_CGC_CLKGATE_HOLDOFF 3
+#define CIO2_CGC_CSI_CLKGATE_HOLDOFF 5
+
+#define CIO2_PXM_FRF_CFG_CRC_TH 16
+
+#define CIO2_INT_EN_EXT_IE_MASK 0xffffffff
+
+#define CIO2_DMA_CHAN 0
+
+#define CIO2_CSIRX_DLY_CNT_CLANE_IDX -1
+
+#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_A 0
+#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_B 0
+#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_A 95
+#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_B -8
+
+#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_A 0
+#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_B 0
+#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_A 85
+#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_B -2
+
+#define CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT 0x4
+#define CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT 0x570
+
+#define CIO2_PMCSR_OFFSET 4
+#define CIO2_PMCSR_D0D3_SHIFT 2
+#define CIO2_PMCSR_D3 0x3
+
+struct cio2_csi2_timing {
+ s32 clk_termen;
+ s32 clk_settle;
+ s32 dat_termen;
+ s32 dat_settle;
+};
+
+struct cio2_buffer {
+ struct vb2_v4l2_buffer vbb;
+ u32 *lop[CIO2_MAX_LOPS];
+ dma_addr_t lop_bus_addr[CIO2_MAX_LOPS];
+ unsigned int offset;
+};
+
+struct csi2_bus_info {
+ u32 port;
+ u32 lanes;
+};
+
+struct cio2_queue {
+ /* mutex to be used by vb2_queue */
+ struct mutex lock;
+ struct media_pipeline pipe;
+ struct csi2_bus_info csi2;
+ struct v4l2_subdev *sensor;
+ void __iomem *csi_rx_base;
+
+ /* Subdev, /dev/v4l-subdevX */
+ struct v4l2_subdev subdev;
+ struct media_pad subdev_pads[CIO2_PADS];
+ struct v4l2_mbus_framefmt subdev_fmt;
+ atomic_t frame_sequence;
+
+ /* Video device, /dev/videoX */
+ struct video_device vdev;
+ struct media_pad vdev_pad;
+ struct v4l2_pix_format_mplane format;
+ struct vb2_queue vbq;
+
+ /* Buffer queue handling */
+ struct cio2_fbpt_entry *fbpt; /* Frame buffer pointer table */
+ dma_addr_t fbpt_bus_addr;
+ struct cio2_buffer *bufs[CIO2_MAX_BUFFERS];
+ unsigned int bufs_first; /* Index of the first used entry */
+ unsigned int bufs_next; /* Index of the first unused entry */
+ atomic_t bufs_queued;
+};
+
+struct cio2_device {
+ struct pci_dev *pci_dev;
+ void __iomem *base;
+ struct v4l2_device v4l2_dev;
+ struct cio2_queue queue[CIO2_QUEUES];
+ struct cio2_queue *cur_queue;
+ /* mutex to be used by video_device */
+ struct mutex lock;
+
+ bool streaming;
+ struct v4l2_async_notifier notifier;
+ struct media_device media_dev;
+
+ /*
+ * Safety net to catch DMA fetch ahead
+ * when reaching the end of LOP
+ */
+ void *dummy_page;
+ /* DMA handle of dummy_page */
+ dma_addr_t dummy_page_bus_addr;
+ /* single List of Pointers (LOP) page */
+ u32 *dummy_lop;
+ /* DMA handle of dummy_lop */
+ dma_addr_t dummy_lop_bus_addr;
+};
+
+/**************** Virtual channel ****************/
+/*
+ * This should come from sensor driver. No
+ * driver interface nor requirement yet.
+ */
+#define SENSOR_VIR_CH_DFLT 0
+
+/**************** FBPT operations ****************/
+#define CIO2_FBPT_SIZE (CIO2_MAX_BUFFERS * CIO2_MAX_LOPS * \
+ sizeof(struct cio2_fbpt_entry))
+
+#define CIO2_FBPT_SUBENTRY_UNIT 4
+#define CIO2_PAGE_SIZE PAGE_SIZE
+
+/* cio2 fbpt first_entry ctrl status */
+#define CIO2_FBPT_CTRL_VALID BIT(0)
+#define CIO2_FBPT_CTRL_IOC BIT(1)
+#define CIO2_FBPT_CTRL_IOS BIT(2)
+#define CIO2_FBPT_CTRL_SUCCXFAIL BIT(3)
+#define CIO2_FBPT_CTRL_CMPLCODE_SHIFT 4
+
+/*
+ * Frame Buffer Pointer Table(FBPT) entry
+ * each entry describe an output buffer and consists of
+ * several sub-entries
+ */
+struct __packed cio2_fbpt_entry {
+ union {
+ struct __packed {
+ u32 ctrl; /* status ctrl */
+ u16 cur_line_num; /* current line # written to DDR */
+ u16 frame_num; /* updated by DMA upon FE */
+ u32 first_page_offset; /* offset for 1st page in LOP */
+ } first_entry;
+ /* Second entry per buffer */
+ struct __packed {
+ u32 timestamp;
+ u32 num_of_bytes;
+ /* the number of bytes for write on last page */
+ u16 last_page_available_bytes;
+ /* the number of pages allocated for this buf */
+ u16 num_of_pages;
+ } second_entry;
+ };
+ u32 lop_page_addr; /* Points to list of pointers (LOP) table */
+};
+
+static inline struct cio2_queue *file_to_cio2_queue(struct file *file)
+{
+ return container_of(video_devdata(file), struct cio2_queue, vdev);
+}
+
+static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq)
+{
+ return container_of(vq, struct cio2_queue, vbq);
+}
+
+#endif
--
2.7.4
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v6 3/3] intel-ipu3: cio2: Add new MIPI-CSI2 driver
2017-10-26 2:24 ` [PATCH v6 3/3] intel-ipu3: cio2: Add new MIPI-CSI2 driver Yong Zhi
@ 2017-10-26 14:42 ` Sakari Ailus
0 siblings, 0 replies; 5+ messages in thread
From: Sakari Ailus @ 2017-10-26 14:42 UTC (permalink / raw)
To: Yong Zhi
Cc: linux-media, sakari.ailus, jian.xu.zheng, tfiga, rajmohan.mani,
tuukka.toivonen, hyungwoo.yang, chiranjeevi.rapolu, jerry.w.hu,
Vijaykumar Ramya
Hi Yong,
Thanks for the update! A few minor comments still... please see below.
On Wed, Oct 25, 2017 at 09:24:42PM -0500, Yong Zhi wrote:
> This patch adds CIO2 CSI-2 device driver for
> Intel's IPU3 camera sub-system support.
>
> Signed-off-by: Yong Zhi <yong.zhi@intel.com>
> Signed-off-by: Hyungwoo Yang <hyungwoo.yang@intel.com>
> Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
> Signed-off-by: Vijaykumar Ramya <ramya.vijaykumar@intel.com>
> ---
> drivers/media/pci/Kconfig | 2 +
> drivers/media/pci/Makefile | 3 +-
> drivers/media/pci/intel/Makefile | 5 +
> drivers/media/pci/intel/ipu3/Kconfig | 19 +
> drivers/media/pci/intel/ipu3/Makefile | 1 +
> drivers/media/pci/intel/ipu3/ipu3-cio2.c | 2014 ++++++++++++++++++++++++++++++
> drivers/media/pci/intel/ipu3/ipu3-cio2.h | 449 +++++++
> 7 files changed, 2492 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/pci/intel/Makefile
> create mode 100644 drivers/media/pci/intel/ipu3/Kconfig
> create mode 100644 drivers/media/pci/intel/ipu3/Makefile
> create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.c
> create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.h
>
> diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
> index da28e68c87d8..5932e225f9c0 100644
> --- a/drivers/media/pci/Kconfig
> +++ b/drivers/media/pci/Kconfig
> @@ -54,5 +54,7 @@ source "drivers/media/pci/smipcie/Kconfig"
> source "drivers/media/pci/netup_unidvb/Kconfig"
> endif
>
> +source "drivers/media/pci/intel/ipu3/Kconfig"
> +
> endif #MEDIA_PCI_SUPPORT
> endif #PCI
> diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
> index a7e8af0f64a7..d8f98434fb8c 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -13,7 +13,8 @@ obj-y += ttpci/ \
> ddbridge/ \
> saa7146/ \
> smipcie/ \
> - netup_unidvb/
> + netup_unidvb/ \
> + intel/
>
> obj-$(CONFIG_VIDEO_IVTV) += ivtv/
> obj-$(CONFIG_VIDEO_ZORAN) += zoran/
> diff --git a/drivers/media/pci/intel/Makefile b/drivers/media/pci/intel/Makefile
> new file mode 100644
> index 000000000000..745c8b2a7819
> --- /dev/null
> +++ b/drivers/media/pci/intel/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# Makefile for the IPU3 cio2 and ImGU drivers
> +#
> +
> +obj-y += ipu3/
> diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig
> new file mode 100644
> index 000000000000..4bb8e0e97e49
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu3/Kconfig
> @@ -0,0 +1,19 @@
> +config VIDEO_IPU3_CIO2
> + tristate "Intel ipu3-cio2 driver"
> + depends on VIDEO_V4L2 && PCI
> + depends on VIDEO_V4L2_SUBDEV_API
> + depends on MEDIA_CONTROLLER
> + depends on X86 || COMPILE_TEST
> + depends on HAS_DMA
> + depends on ACPI
> + select V4L2_FWNODE
> + select VIDEOBUF2_DMA_SG
> +
> + ---help---
> + This is the Intel IPU3 CIO2 CSI-2 receiver unit, found in Intel
> + Skylake and Kaby Lake SoCs and used for capturing images and
> + video from a camera sensor.
> +
> + Say Y or M here if you have a Skylake/Kaby Lake SoC with MIPI CSI-2
> + connected camera.
> + The module will be called ipu3-cio2.
> diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile
> new file mode 100644
> index 000000000000..20186e3ff2ae
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu3/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o
> diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
> new file mode 100644
> index 000000000000..8c0feda77c74
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
> @@ -0,0 +1,2014 @@
> +/*
> + * Copyright (c) 2017 Intel Corporation.
> + *
> + * 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.
> + *
> + * Based partially on Intel IPU4 driver written by
> + * Sakari Ailus <sakari.ailus@linux.intel.com>
> + * Samu Onkalo <samu.onkalo@intel.com>
> + * Jouni Högander <jouni.hogander@intel.com>
> + * Jouni Ukkonen <jouni.ukkonen@intel.com>
> + * Antti Laakso <antti.laakso@intel.com>
> + * et al.
> + *
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/property.h>
> +#include <linux/vmalloc.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "ipu3-cio2.h"
> +
> +struct ipu3_cio2_fmt {
> + u32 mbus_code;
> + u32 fourcc;
> + u8 mipicode;
> +};
> +
> +/*
> + * These are raw formats used in Intel's third generation of
> + * Image Processing Unit known as IPU3.
> + * 10bit raw bayer packed, 32 bytes for every 25 pixels,
> + * last LSB 6 bits unused.
> + */
> +static const struct ipu3_cio2_fmt formats[] = {
> + { /* put default entry at beginning */
> + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .fourcc = V4L2_PIX_FMT_IPU3_SGRBG10,
> + .mipicode = 0x2b,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .fourcc = V4L2_PIX_FMT_IPU3_SGBRG10,
> + .mipicode = 0x2b,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .fourcc = V4L2_PIX_FMT_IPU3_SBGGR10,
> + .mipicode = 0x2b,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .fourcc = V4L2_PIX_FMT_IPU3_SRGGB10,
> + .mipicode = 0x2b,
> + },
> +};
> +
> +/*
> + * cio2_find_format - lookup color format by fourcc or/and media bus code
> + * @pixelformat: fourcc to match, ignored if null
> + * @mbus_code: media bus code to match, ignored if null
> + */
> +static const struct ipu3_cio2_fmt *cio2_find_format(const u32 *pixelformat,
> + const u32 *mbus_code)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(formats); i++) {
> + if (pixelformat && *pixelformat != formats[i].fourcc)
> + continue;
> + if (mbus_code && *mbus_code != formats[i].mbus_code)
> + continue;
> +
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static inline u32 cio2_bytesperline(const unsigned int width)
> +{
> + /*
> + * 64 bytes for every 50 pixels, the line length
> + * in bytes is multiple of 64 (line end alignment).
> + */
> + return DIV_ROUND_UP(width, 50) * 64;
> +}
> +
> +/**************** FBPT operations ****************/
> +
> +static void cio2_fbpt_exit_dummy(struct cio2_device *cio2)
> +{
> + if (cio2->dummy_lop) {
> + dma_free_coherent(&cio2->pci_dev->dev, CIO2_PAGE_SIZE,
> + cio2->dummy_lop, cio2->dummy_lop_bus_addr);
> + cio2->dummy_lop = NULL;
> + }
> + if (cio2->dummy_page) {
> + dma_free_coherent(&cio2->pci_dev->dev, CIO2_PAGE_SIZE,
> + cio2->dummy_page, cio2->dummy_page_bus_addr);
> + cio2->dummy_page = NULL;
> + }
> +}
> +
> +static int cio2_fbpt_init_dummy(struct cio2_device *cio2)
> +{
> + unsigned int i;
> +
> + cio2->dummy_page = dma_alloc_coherent(&cio2->pci_dev->dev,
> + CIO2_PAGE_SIZE,
> + &cio2->dummy_page_bus_addr,
> + GFP_KERNEL);
> + cio2->dummy_lop = dma_alloc_coherent(&cio2->pci_dev->dev,
> + CIO2_PAGE_SIZE,
> + &cio2->dummy_lop_bus_addr,
> + GFP_KERNEL);
> + if (!cio2->dummy_page || !cio2->dummy_lop) {
> + cio2_fbpt_exit_dummy(cio2);
> + return -ENOMEM;
> + }
> + /*
> + * List of Pointers(LOP) contains 1024x32b pointers to 4KB page each
> + * Initialize each entry to dummy_page bus base address.
> + */
> + for (i = 0; i < CIO2_PAGE_SIZE / sizeof(*cio2->dummy_lop); i++)
> + cio2->dummy_lop[i] = cio2->dummy_page_bus_addr >> PAGE_SHIFT;
> +
> + return 0;
> +}
> +
> +static void cio2_fbpt_entry_enable(struct cio2_device *cio2,
> + struct cio2_fbpt_entry entry[CIO2_MAX_LOPS])
> +{
> + /*
> + * The CPU first initializes some fields in fbpt, then sets
> + * the VALID bit, this barrier is to ensure that the DMA(device)
> + * does not see the VALID bit enabled before other fields are
> + * initialized; otherwise it could lead to havoc.
> + */
> + dma_wmb();
> +
> + /*
> + * Request interrupts for start and completion
> + * Valid bit is applicable only to 1st entry
> + */
> + entry[0].first_entry.ctrl = CIO2_FBPT_CTRL_VALID |
> + CIO2_FBPT_CTRL_IOC | CIO2_FBPT_CTRL_IOS;
> +}
> +
> +/* Initialize fpbt entries to point to dummy frame */
> +static void cio2_fbpt_entry_init_dummy(struct cio2_device *cio2,
> + struct cio2_fbpt_entry
> + entry[CIO2_MAX_LOPS])
> +{
> + unsigned int i;
> +
> + entry[0].first_entry.first_page_offset = 0;
> + entry[1].second_entry.num_of_pages =
> + CIO2_PAGE_SIZE / sizeof(u32) * CIO2_MAX_LOPS;
> + entry[1].second_entry.last_page_available_bytes = CIO2_PAGE_SIZE - 1;
> +
> + for (i = 0; i < CIO2_MAX_LOPS; i++)
> + entry[i].lop_page_addr = cio2->dummy_lop_bus_addr >> PAGE_SHIFT;
> +
> + cio2_fbpt_entry_enable(cio2, entry);
> +}
> +
> +/* Initialize fpbt entries to point to a given buffer */
> +static void cio2_fbpt_entry_init_buf(struct cio2_device *cio2,
> + struct cio2_buffer *b,
> + struct cio2_fbpt_entry
> + entry[CIO2_MAX_LOPS])
> +{
> + struct vb2_buffer *vb = &b->vbb.vb2_buf;
> + unsigned int length = vb->planes[0].length;
> + int remaining, i;
> +
> + entry[0].first_entry.first_page_offset = b->offset;
> + remaining = length + entry[0].first_entry.first_page_offset;
> + entry[1].second_entry.num_of_pages =
> + DIV_ROUND_UP(remaining, CIO2_PAGE_SIZE);
> + /*
> + * last_page_available_bytes has the offset of the last byte in the
> + * last page which is still accessible by DMA. DMA cannot access
> + * beyond this point. Valid range for this is from 0 to 4095.
> + * 0 indicates 1st byte in the page is DMA accessible.
> + * 4095 (CIO2_PAGE_SIZE - 1) means every single byte in the last page
> + * is available for DMA transfer.
> + */
> + entry[1].second_entry.last_page_available_bytes =
> + (remaining & ~PAGE_MASK) ?
> + (remaining & ~PAGE_MASK) - 1 :
> + CIO2_PAGE_SIZE - 1;
> + /* Fill FBPT */
> + remaining = length;
> + i = 0;
> + while (remaining > 0) {
> + entry->lop_page_addr = b->lop_bus_addr[i] >> PAGE_SHIFT;
> + remaining -= CIO2_PAGE_SIZE / sizeof(u32) * CIO2_PAGE_SIZE;
> + entry++;
> + i++;
> + }
> +
> + /*
> + * The first not meaningful FBPT entry should point to a valid LOP
> + */
> + entry->lop_page_addr = cio2->dummy_lop_bus_addr >> PAGE_SHIFT;
> +
> + cio2_fbpt_entry_enable(cio2, entry);
> +}
> +
> +static int cio2_fbpt_init(struct cio2_device *cio2, struct cio2_queue *q)
> +{
> + struct device *dev = &cio2->pci_dev->dev;
> +
> + q->fbpt = dma_alloc_coherent(dev, CIO2_FBPT_SIZE, &q->fbpt_bus_addr,
> + GFP_KERNEL);
> + if (!q->fbpt)
> + return -ENOMEM;
> +
> + memset(q->fbpt, 0, CIO2_FBPT_SIZE);
> +
> + return 0;
> +}
> +
> +static int cio2_fbpt_rearrange(struct cio2_device *cio2, struct cio2_queue *q)
> +{
> + struct cio2_fbpt_entry *fbpt_temp;
> + int i, j;
> + int size, entries;
These won't be negative, right? unsigned int.
> + struct cio2_buffer *bufs[CIO2_MAX_BUFFERS];
> +
> + for (i = 0, j = q->bufs_first; i < CIO2_MAX_BUFFERS;
> + i++, j = (j + 1) % CIO2_MAX_BUFFERS)
> + if (q->bufs[j])
> + break;
> +
> + if (i == CIO2_MAX_BUFFERS)
> + return 0;
> +
> + if (j) {
> + fbpt_temp = vmalloc(CIO2_FBPT_SIZE);
> + if (!fbpt_temp)
> + return -ENOMEM;
> +
> + entries = (CIO2_MAX_BUFFERS - j) * CIO2_MAX_LOPS;
> + size = entries * sizeof(struct cio2_fbpt_entry);
> +
> + memcpy(fbpt_temp, q->fbpt + j * CIO2_MAX_LOPS, size);
> + memcpy(fbpt_temp + entries, q->fbpt, CIO2_FBPT_SIZE - size);
> + memcpy(q->fbpt, fbpt_temp, CIO2_FBPT_SIZE);
> + memcpy(bufs, q->bufs + j,
> + (CIO2_MAX_BUFFERS - j) * sizeof(*bufs));
> + memcpy(bufs + CIO2_MAX_BUFFERS - j, q->bufs, j * sizeof(*bufs));
> + memcpy(q->bufs, bufs, CIO2_MAX_BUFFERS * sizeof(*bufs));
> +
> + vfree(fbpt_temp);
What will happen if memory allocation here fails? Suspend still proceeds
independently of the error.
Could you use this, please, with possibly renaming and making the function
static?
void arrange(void *ptr, size_t elem_size, size_t elems, size_t start)
{
struct {
size_t begin, end;
} arr[2] = {
{ 0, start - 1 },
{ start, elems - 1 },
};
u8 tmp[elem_size];
#define arr_size(a) ((a)->end - (a)->begin + 1)
/* Loop as long as we need have out-of-place entries */
while (arr_size(&arr[0]) && arr_size(&arr[1])) {
size_t size0, i;
/*
* Find the number of entries that can be arranged on this
* iteration.
*/
if (arr_size(&arr[0]) > arr_size(&arr[1]))
size0 = arr_size(&arr[1]);
else
size0 = arr_size(&arr[0]);
/* Swap the entries in two parts of the array. */
for (i = 0; i < size0; i++) {
memcpy(tmp, ptr + elem_size * (arr[1].begin + i),
elem_size);
memcpy(ptr + elem_size * (arr[1].begin + i),
ptr + elem_size * (arr[0].begin + i), elem_size);
memcpy(ptr + elem_size * (arr[0].begin + i), tmp,
elem_size);
}
if (arr_size(&arr[0]) > arr_size(&arr[1])) {
/* The end of the first array remains unarranged. */
arr[0].begin += size0;
} else {
/*
* The first array is fully arranged so we proceed
* handling the next one.
*/
arr[0].begin = arr[1].begin;
arr[0].end = arr[1].begin + size0 - 1;
arr[1].begin += size0;
}
}
}
> + }
> +
> + /*
> + * DMA clears the valid bit when accessing the buffer.
> + * When stopping stream in suspend callback, some of the buffers
> + * may be in invalid state. After resume, when DMA meets the invalid
> + * buffer, it will halt and stop receiving new data.
> + * To avoid DMA halting, set the valid bit for all buffers in FBPT.
> + */
> + for (i = 0; i < CIO2_MAX_BUFFERS; i++)
> + cio2_fbpt_entry_enable(cio2, q->fbpt + i * CIO2_MAX_LOPS);
> +
> + return 0;
> +}
> +
> +static void cio2_fbpt_exit(struct cio2_queue *q, struct device *dev)
> +{
> + dma_free_coherent(dev, CIO2_FBPT_SIZE, q->fbpt, q->fbpt_bus_addr);
> +}
> +
> +/**************** CSI2 hardware setup ****************/
> +
> +/*
> + * The CSI2 receiver has several parameters affecting
> + * the receiver timings. These depend on the MIPI bus frequency
> + * F in Hz (sensor transmitter rate) as follows:
> + * register value = (A/1e9 + B * UI) / COUNT_ACC
> + * where
> + * UI = 1 / (2 * F) in seconds
> + * COUNT_ACC = counter accuracy in seconds
> + * For IPU3 COUNT_ACC = 0.0625
> + *
> + * A and B are coefficients from the table below,
> + * depending whether the register minimum or maximum value is
> + * calculated.
> + * Minimum Maximum
> + * Clock lane A B A B
> + * reg_rx_csi_dly_cnt_termen_clane 0 0 38 0
> + * reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16
> + * Data lanes
> + * reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4
> + * reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6
> + * reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4
> + * reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6
> + * reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4
> + * reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6
> + * reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4
> + * reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6
> + *
> + * We use the minimum values of both A and B.
> + */
> +
> +/*
> + * shift for keeping value range suitable for 32-bit integer arithmetics
> + */
> +#define LIMIT_SHIFT 8
> +
> +static s32 cio2_rx_timing(s32 a, s32 b, s64 freq, int def)
> +{
> + const u32 accinv = 16; /* invert of counter resolution */
> + const u32 uiinv = 500000000; /* 1e9 / 2 */
> + s32 r;
> +
> + freq >>= LIMIT_SHIFT;
> +
> + if (WARN_ON(freq <= 0 || freq > S32_MAX))
> + return def;
> + /*
> + * b could be 0, -2 or -8, so |accinv * b| is always
> + * less than (1 << ds) and thus |r| < 500000000.
> + */
> + r = accinv * b * (uiinv >> LIMIT_SHIFT);
> + r = r / (s32)freq;
> + /* max value of a is 95 */
> + r += accinv * a;
> +
> + return r;
> +};
> +
> +/* Calculate the the delay value for termination enable of clock lane HS Rx */
> +static int cio2_csi2_calc_timing(struct cio2_device *cio2, struct cio2_queue *q,
> + struct cio2_csi2_timing *timing)
> +{
> + struct device *dev = &cio2->pci_dev->dev;
> + struct v4l2_querymenu qm = {.id = V4L2_CID_LINK_FREQ, };
> + struct v4l2_ctrl *link_freq;
> + s64 freq;
> + int r;
> +
> + if (!q->sensor)
> + return -ENODEV;
> +
> + link_freq = v4l2_ctrl_find(q->sensor->ctrl_handler, V4L2_CID_LINK_FREQ);
> + if (!link_freq) {
> + dev_err(dev, "failed to find LINK_FREQ\n");
> + return -EPIPE;
> + };
> +
> + qm.index = v4l2_ctrl_g_ctrl(link_freq);
> + r = v4l2_querymenu(q->sensor->ctrl_handler, &qm);
> + if (r) {
> + dev_err(dev, "failed to get menu item\n");
> + return r;
> + }
> +
> + if (!qm.value) {
> + dev_err(dev, "error invalid link_freq\n");
> + return -EINVAL;
> + }
> + freq = qm.value;
> +
> + timing->clk_termen = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_A,
> + CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_B,
> + freq,
> + CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT);
> + timing->clk_settle = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_A,
> + CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_B,
> + freq,
> + CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT);
> + timing->dat_termen = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_A,
> + CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_B,
> + freq,
> + CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT);
> + timing->dat_settle = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_A,
> + CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_B,
> + freq,
> + CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT);
> +
> + dev_dbg(dev, "freq ct value is %d\n", timing->clk_termen);
> + dev_dbg(dev, "freq cs value is %d\n", timing->clk_settle);
> + dev_dbg(dev, "freq dt value is %d\n", timing->dat_termen);
> + dev_dbg(dev, "freq ds value is %d\n", timing->dat_settle);
> +
> + return 0;
> +};
> +
> +static int cio2_hw_init(struct cio2_device *cio2, struct cio2_queue *q)
> +{
> + static const int NUM_VCS = 4;
> + static const int SID; /* Stream id */
> + static const int ENTRY;
> + static const int FBPT_WIDTH = DIV_ROUND_UP(CIO2_MAX_LOPS,
> + CIO2_FBPT_SUBENTRY_UNIT);
> + const u32 num_buffers1 = CIO2_MAX_BUFFERS - 1;
> + const struct ipu3_cio2_fmt *fmt;
> + void __iomem *const base = cio2->base;
> + u8 lanes, csi2bus = q->csi2.port;
> + u8 sensor_vc = SENSOR_VIR_CH_DFLT;
> + struct cio2_csi2_timing timing;
> + int i, r;
> +
> + fmt = cio2_find_format(NULL, &q->subdev_fmt.code);
> + if (!fmt)
> + return -EINVAL;
> +
> + lanes = q->csi2.lanes;
> +
> + r = cio2_csi2_calc_timing(cio2, q, &timing);
> + if (r)
> + return r;
> +
> + writel(timing.clk_termen, q->csi_rx_base +
> + CIO2_REG_CSIRX_DLY_CNT_TERMEN(CIO2_CSIRX_DLY_CNT_CLANE_IDX));
> + writel(timing.clk_settle, q->csi_rx_base +
> + CIO2_REG_CSIRX_DLY_CNT_SETTLE(CIO2_CSIRX_DLY_CNT_CLANE_IDX));
> +
> + for (i = 0; i < lanes; i++) {
> + writel(timing.dat_termen, q->csi_rx_base +
> + CIO2_REG_CSIRX_DLY_CNT_TERMEN(i));
> + writel(timing.dat_settle, q->csi_rx_base +
> + CIO2_REG_CSIRX_DLY_CNT_SETTLE(i));
> + }
> +
> + writel(CIO2_PBM_WMCTRL1_MIN_2CK |
> + CIO2_PBM_WMCTRL1_MID1_2CK |
> + CIO2_PBM_WMCTRL1_MID2_2CK, base + CIO2_REG_PBM_WMCTRL1);
> + writel(CIO2_PBM_WMCTRL2_HWM_2CK << CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT |
> + CIO2_PBM_WMCTRL2_LWM_2CK << CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT |
> + CIO2_PBM_WMCTRL2_OBFFWM_2CK <<
> + CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT |
> + CIO2_PBM_WMCTRL2_TRANSDYN << CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT |
> + CIO2_PBM_WMCTRL2_OBFF_MEM_EN, base + CIO2_REG_PBM_WMCTRL2);
> + writel(CIO2_PBM_ARB_CTRL_LANES_DIV <<
> + CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT |
> + CIO2_PBM_ARB_CTRL_LE_EN |
> + CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN <<
> + CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT |
> + CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP <<
> + CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT,
> + base + CIO2_REG_PBM_ARB_CTRL);
> + writel(CIO2_CSIRX_STATUS_DLANE_HS_MASK,
> + q->csi_rx_base + CIO2_REG_CSIRX_STATUS_DLANE_HS);
> + writel(CIO2_CSIRX_STATUS_DLANE_LP_MASK,
> + q->csi_rx_base + CIO2_REG_CSIRX_STATUS_DLANE_LP);
> +
> + writel(CIO2_FB_HPLL_FREQ, base + CIO2_REG_FB_HPLL_FREQ);
> + writel(CIO2_ISCLK_RATIO, base + CIO2_REG_ISCLK_RATIO);
> +
> + /* Configure MIPI backend */
> + for (i = 0; i < NUM_VCS; i++)
> + writel(1, q->csi_rx_base + CIO2_REG_MIPIBE_SP_LUT_ENTRY(i));
> +
> + /* There are 16 short packet LUT entry */
> + for (i = 0; i < 16; i++)
> + writel(CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD,
> + q->csi_rx_base + CIO2_REG_MIPIBE_LP_LUT_ENTRY(i));
> + writel(CIO2_MIPIBE_GLOBAL_LUT_DISREGARD,
> + q->csi_rx_base + CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD);
> +
> + writel(CIO2_INT_EN_EXT_IE_MASK, base + CIO2_REG_INT_EN_EXT_IE);
> + writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_MASK);
> + writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_ENABLE);
> + writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_EDGE);
> + writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_LEVEL_NOT_PULSE);
> + writel(CIO2_INT_EN_EXT_OE_MASK, base + CIO2_REG_INT_EN_EXT_OE);
> +
> + writel(CIO2_REG_INT_EN_IRQ | CIO2_INT_IOC(CIO2_DMA_CHAN) |
> + CIO2_REG_INT_EN_IOS(CIO2_DMA_CHAN),
> + base + CIO2_REG_INT_EN);
> +
> + writel((CIO2_PXM_PXF_FMT_CFG_BPP_10 | CIO2_PXM_PXF_FMT_CFG_PCK_64B)
> + << CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT,
> + base + CIO2_REG_PXM_PXF_FMT_CFG0(csi2bus));
> + writel(SID << CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT |
> + sensor_vc << CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT |
> + fmt->mipicode << CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT,
> + q->csi_rx_base + CIO2_REG_MIPIBE_LP_LUT_ENTRY(ENTRY));
> + writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_COMP_FORMAT(sensor_vc));
> + writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_FORCE_RAW8);
> + writel(0, base + CIO2_REG_PXM_SID2BID0(csi2bus));
> +
> + writel(lanes, q->csi_rx_base + CIO2_REG_CSIRX_NOF_ENABLED_LANES);
> + writel(CIO2_CGC_PRIM_TGE |
> + CIO2_CGC_SIDE_TGE |
> + CIO2_CGC_XOSC_TGE |
> + CIO2_CGC_D3I3_TGE |
> + CIO2_CGC_CSI2_INTERFRAME_TGE |
> + CIO2_CGC_CSI2_PORT_DCGE |
> + CIO2_CGC_SIDE_DCGE |
> + CIO2_CGC_PRIM_DCGE |
> + CIO2_CGC_ROSC_DCGE |
> + CIO2_CGC_XOSC_DCGE |
> + CIO2_CGC_CLKGATE_HOLDOFF << CIO2_CGC_CLKGATE_HOLDOFF_SHIFT |
> + CIO2_CGC_CSI_CLKGATE_HOLDOFF
> + << CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT, base + CIO2_REG_CGC);
> + writel(CIO2_LTRCTRL_LTRDYNEN, base + CIO2_REG_LTRCTRL);
> + writel(CIO2_LTRVAL0_VAL << CIO2_LTRVAL02_VAL_SHIFT |
> + CIO2_LTRVAL0_SCALE << CIO2_LTRVAL02_SCALE_SHIFT |
> + CIO2_LTRVAL1_VAL << CIO2_LTRVAL13_VAL_SHIFT |
> + CIO2_LTRVAL1_SCALE << CIO2_LTRVAL13_SCALE_SHIFT,
> + base + CIO2_REG_LTRVAL01);
> + writel(CIO2_LTRVAL2_VAL << CIO2_LTRVAL02_VAL_SHIFT |
> + CIO2_LTRVAL2_SCALE << CIO2_LTRVAL02_SCALE_SHIFT |
> + CIO2_LTRVAL3_VAL << CIO2_LTRVAL13_VAL_SHIFT |
> + CIO2_LTRVAL3_SCALE << CIO2_LTRVAL13_SCALE_SHIFT,
> + base + CIO2_REG_LTRVAL23);
> +
> + for (i = 0; i < CIO2_NUM_DMA_CHAN; i++) {
> + writel(0, base + CIO2_REG_CDMABA(i));
> + writel(0, base + CIO2_REG_CDMAC0(i));
> + writel(0, base + CIO2_REG_CDMAC1(i));
> + }
> +
> + /* Enable DMA */
> + writel(q->fbpt_bus_addr >> PAGE_SHIFT,
> + base + CIO2_REG_CDMABA(CIO2_DMA_CHAN));
> +
> + writel(num_buffers1 << CIO2_CDMAC0_FBPT_LEN_SHIFT |
> + FBPT_WIDTH << CIO2_CDMAC0_FBPT_WIDTH_SHIFT |
> + CIO2_CDMAC0_DMA_INTR_ON_FE |
> + CIO2_CDMAC0_FBPT_UPDATE_FIFO_FULL |
> + CIO2_CDMAC0_DMA_EN |
> + CIO2_CDMAC0_DMA_INTR_ON_FS |
> + CIO2_CDMAC0_DMA_HALTED, base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN));
> +
> + writel(1 << CIO2_CDMAC1_LINENUMUPDATE_SHIFT,
> + base + CIO2_REG_CDMAC1(CIO2_DMA_CHAN));
> +
> + writel(0, base + CIO2_REG_PBM_FOPN_ABORT);
> +
> + writel(CIO2_PXM_FRF_CFG_CRC_TH << CIO2_PXM_FRF_CFG_CRC_TH_SHIFT |
> + CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR |
> + CIO2_PXM_FRF_CFG_MSK_ECC_RE |
> + CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE,
> + base + CIO2_REG_PXM_FRF_CFG(q->csi2.port));
> +
> + /* Clear interrupts */
> + writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_CLEAR);
> + writel(~0, base + CIO2_REG_INT_STS_EXT_OE);
> + writel(~0, base + CIO2_REG_INT_STS_EXT_IE);
> + writel(~0, base + CIO2_REG_INT_STS);
> +
> + /* Enable devices, starting from the last device in the pipe */
> + writel(1, q->csi_rx_base + CIO2_REG_MIPIBE_ENABLE);
> + writel(1, q->csi_rx_base + CIO2_REG_CSIRX_ENABLE);
> +
> + return 0;
> +}
> +
> +static void cio2_hw_exit(struct cio2_device *cio2, struct cio2_queue *q)
> +{
> + void __iomem *base = cio2->base;
> + unsigned int i, maxloops = 1000;
> +
> + /* Disable CSI receiver and MIPI backend devices */
> + writel(0, q->csi_rx_base + CIO2_REG_CSIRX_ENABLE);
> + writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_ENABLE);
> +
> + /* Halt DMA */
> + writel(0, base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN));
> + do {
> + if (readl(base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN)) &
> + CIO2_CDMAC0_DMA_HALTED)
> + break;
> + usleep_range(1000, 2000);
> + } while (--maxloops);
> + if (!maxloops)
> + dev_err(&cio2->pci_dev->dev,
> + "DMA %i can not be halted\n", CIO2_DMA_CHAN);
> +
> + for (i = 0; i < CIO2_NUM_PORTS; i++) {
> + writel(readl(base + CIO2_REG_PXM_FRF_CFG(i)) |
> + CIO2_PXM_FRF_CFG_ABORT, base + CIO2_REG_PXM_FRF_CFG(i));
> + writel(readl(base + CIO2_REG_PBM_FOPN_ABORT) |
> + CIO2_PBM_FOPN_ABORT(i), base + CIO2_REG_PBM_FOPN_ABORT);
> + }
> +}
> +
> +static void cio2_buffer_done(struct cio2_device *cio2, unsigned int dma_chan)
> +{
> + struct device *dev = &cio2->pci_dev->dev;
> + struct cio2_queue *q = cio2->cur_queue;
> + int buffers_found = 0;
> + u64 ns = ktime_get_ns();
> +
> + if (dma_chan >= CIO2_QUEUES) {
> + dev_err(dev, "bad DMA channel %i\n", dma_chan);
> + return;
> + }
> +
> + /* Find out which buffer(s) are ready */
> + do {
> + struct cio2_fbpt_entry *const entry =
> + &q->fbpt[q->bufs_first * CIO2_MAX_LOPS];
> + struct cio2_buffer *b;
> +
> + if (entry->first_entry.ctrl & CIO2_FBPT_CTRL_VALID)
> + break;
> +
> + b = q->bufs[q->bufs_first];
> + if (b) {
> + unsigned int bytes = entry[1].second_entry.num_of_bytes;
> +
> + q->bufs[q->bufs_first] = NULL;
> + atomic_dec(&q->bufs_queued);
> + dev_dbg(&cio2->pci_dev->dev,
> + "buffer %i done\n", b->vbb.vb2_buf.index);
> +
> + b->vbb.vb2_buf.timestamp = ns;
> + b->vbb.field = V4L2_FIELD_NONE;
> + b->vbb.sequence = atomic_read(&q->frame_sequence);
> + if (b->vbb.vb2_buf.planes[0].length != bytes)
> + dev_warn(dev, "buffer length is %d received %d\n",
> + b->vbb.vb2_buf.planes[0].length,
> + bytes);
> + vb2_buffer_done(&b->vbb.vb2_buf, VB2_BUF_STATE_DONE);
> + }
> + atomic_inc(&q->frame_sequence);
> + cio2_fbpt_entry_init_dummy(cio2, entry);
> + q->bufs_first = (q->bufs_first + 1) % CIO2_MAX_BUFFERS;
> + buffers_found++;
> + } while (1);
> +
> + if (buffers_found == 0)
> + dev_warn(&cio2->pci_dev->dev,
> + "no ready buffers found on DMA channel %u\n",
> + dma_chan);
> +}
> +
> +static void cio2_queue_event_sof(struct cio2_device *cio2, struct cio2_queue *q)
> +{
> + /*
> + * For the user space camera control algorithms it is essential
> + * to know when the reception of a frame has begun. That's often
> + * the best timing information to get from the hardware.
> + */
> + struct v4l2_event event = {
> + .type = V4L2_EVENT_FRAME_SYNC,
> + .u.frame_sync.frame_sequence = atomic_read(&q->frame_sequence),
> + };
> +
> + v4l2_event_queue(q->subdev.devnode, &event);
> +}
> +
> +static const char *const cio2_irq_errs[] = {
> + "single packet header error corrected",
> + "multiple packet header errors detected",
> + "payload checksum (CRC) error",
> + "fifo overflow",
> + "reserved short packet data type detected",
> + "reserved long packet data type detected",
> + "incomplete long packet detected",
> + "frame sync error",
> + "line sync error",
> + "DPHY start of transmission error",
> + "DPHY synchronization error",
> + "escape mode error",
> + "escape mode trigger event",
> + "escape mode ultra-low power state for data lane(s)",
> + "escape mode ultra-low power state exit for clock lane",
> + "inter-frame short packet discarded",
> + "inter-frame long packet discarded",
> + "non-matching Long Packet stalled",
> +};
> +
> +static const char *const cio2_port_errs[] = {
> + "ECC recoverable",
> + "DPHY not recoverable",
> + "ECC not recoverable",
> + "CRC error",
> + "INTERFRAMEDATA",
> + "PKT2SHORT",
> + "PKT2LONG",
> +};
> +
> +static irqreturn_t cio2_irq(int irq, void *cio2_ptr)
> +{
> + struct cio2_device *cio2 = cio2_ptr;
> + void __iomem *const base = cio2->base;
> + struct device *dev = &cio2->pci_dev->dev;
> + u32 int_status, int_clear;
> +
> + int_status = readl(base + CIO2_REG_INT_STS);
> + int_clear = int_status;
> +
> + if (!int_status)
> + return IRQ_NONE;
> +
> + if (int_status & CIO2_INT_IOOE) {
> + /*
> + * Interrupt on Output Error:
> + * 1) SRAM is full and FS received, or
> + * 2) An invalid bit detected by DMA.
> + */
> + u32 oe_status, oe_clear;
> +
> + oe_clear = readl(base + CIO2_REG_INT_STS_EXT_OE);
> + oe_status = oe_clear;
> +
> + if (oe_status & CIO2_INT_EXT_OE_DMAOE_MASK) {
> + dev_err(dev, "DMA output error: 0x%x\n",
> + (oe_status & CIO2_INT_EXT_OE_DMAOE_MASK)
> + >> CIO2_INT_EXT_OE_DMAOE_SHIFT);
> + oe_status &= ~CIO2_INT_EXT_OE_DMAOE_MASK;
> + }
> + if (oe_status & CIO2_INT_EXT_OE_OES_MASK) {
> + dev_err(dev, "DMA output error on CSI2 buses: 0x%x\n",
> + (oe_status & CIO2_INT_EXT_OE_OES_MASK)
> + >> CIO2_INT_EXT_OE_OES_SHIFT);
> + oe_status &= ~CIO2_INT_EXT_OE_OES_MASK;
> + }
> + writel(oe_clear, base + CIO2_REG_INT_STS_EXT_OE);
> + if (oe_status)
> + dev_warn(dev, "unknown interrupt 0x%x on OE\n",
> + oe_status);
> + int_status &= ~CIO2_INT_IOOE;
> + }
> +
> + if (int_status & CIO2_INT_IOC_MASK) {
> + /* DMA IO done -- frame ready */
> + u32 clr = 0;
> + unsigned int d;
> +
> + for (d = 0; d < CIO2_NUM_DMA_CHAN; d++)
> + if (int_status & CIO2_INT_IOC(d)) {
> + clr |= CIO2_INT_IOC(d);
> + cio2_buffer_done(cio2, d);
> + }
> + int_status &= ~clr;
> + }
> +
> + if (int_status & CIO2_INT_IOS_IOLN_MASK) {
> + /* DMA IO starts or reached specified line */
> + u32 clr = 0;
> + unsigned int d;
> +
> + for (d = 0; d < CIO2_NUM_DMA_CHAN; d++)
> + if (int_status & CIO2_INT_IOS_IOLN(d)) {
> + clr |= CIO2_INT_IOS_IOLN(d);
> + if (d == CIO2_DMA_CHAN)
> + cio2_queue_event_sof(cio2,
> + cio2->cur_queue);
> + }
> + int_status &= ~clr;
> + }
> +
> + if (int_status & (CIO2_INT_IOIE | CIO2_INT_IOIRQ)) {
> + /* CSI2 receiver (error) interrupt */
> + u32 ie_status, ie_clear;
> + unsigned int port;
> +
> + ie_clear = readl(base + CIO2_REG_INT_STS_EXT_IE);
> + ie_status = ie_clear;
> +
> + for (port = 0; port < CIO2_NUM_PORTS; port++) {
> + u32 port_status = (ie_status >> (port * 8)) & 0xff;
> + u32 err_mask = BIT_MASK(ARRAY_SIZE(cio2_port_errs)) - 1;
> + void __iomem *const csi_rx_base =
> + base + CIO2_REG_PIPE_BASE(port);
> + unsigned int i;
> +
> + while (port_status & err_mask) {
> + i = ffs(port_status) - 1;
> + dev_err(dev, "port %i error %s\n",
> + port, cio2_port_errs[i]);
> + ie_status &= ~BIT(port * 8 + i);
> + port_status &= ~BIT(i);
> + }
> +
> + if (ie_status & CIO2_INT_EXT_IE_IRQ(port)) {
> + u32 csi2_status, csi2_clear;
> +
> + csi2_status = readl(csi_rx_base +
> + CIO2_REG_IRQCTRL_STATUS);
> + csi2_clear = csi2_status;
> + err_mask =
> + BIT_MASK(ARRAY_SIZE(cio2_irq_errs)) - 1;
> +
> + while (csi2_status & err_mask) {
> + i = ffs(csi2_status) - 1;
> + dev_err(dev,
> + "CSI-2 receiver port %i: %s\n",
> + port, cio2_irq_errs[i]);
> + csi2_status &= ~BIT(i);
> + }
> +
> + writel(csi2_clear,
> + csi_rx_base + CIO2_REG_IRQCTRL_CLEAR);
> + if (csi2_status)
> + dev_warn(dev,
> + "unknown CSI2 error 0x%x on port %i\n",
> + csi2_status, port);
> +
> + ie_status &= ~CIO2_INT_EXT_IE_IRQ(port);
> + }
> + }
> +
> + writel(ie_clear, base + CIO2_REG_INT_STS_EXT_IE);
> + if (ie_status)
> + dev_warn(dev, "unknown interrupt 0x%x on IE\n",
> + ie_status);
> +
> + int_status &= ~(CIO2_INT_IOIE | CIO2_INT_IOIRQ);
> + }
> +
> + writel(int_clear, base + CIO2_REG_INT_STS);
> + if (int_status)
> + dev_warn(dev, "unknown interrupt 0x%x on INT\n", int_status);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/**************** Videobuf2 interface ****************/
> +
> +static void cio2_vb2_return_all_buffers(struct cio2_queue *q)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < CIO2_MAX_BUFFERS; i++) {
> + if (q->bufs[i]) {
> + atomic_dec(&q->bufs_queued);
> + vb2_buffer_done(&q->bufs[i]->vbb.vb2_buf,
> + VB2_BUF_STATE_ERROR);
> + }
> + }
> +}
> +
> +static int cio2_vb2_queue_setup(struct vb2_queue *vq,
> + unsigned int *num_buffers,
> + unsigned int *num_planes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct cio2_device *cio2 = vb2_get_drv_priv(vq);
> + struct cio2_queue *q = vb2q_to_cio2_queue(vq);
> + unsigned int i;
> +
> + *num_planes = q->format.num_planes;
> +
> + for (i = 0; i < *num_planes; ++i) {
> + sizes[i] = q->format.plane_fmt[i].sizeimage;
> + alloc_devs[i] = &cio2->pci_dev->dev;
> + }
> +
> + *num_buffers = clamp_val(*num_buffers, 1, CIO2_MAX_BUFFERS);
> +
> + /* Initialize buffer queue */
> + for (i = 0; i < CIO2_MAX_BUFFERS; i++) {
> + q->bufs[i] = NULL;
> + cio2_fbpt_entry_init_dummy(cio2, &q->fbpt[i * CIO2_MAX_LOPS]);
> + }
> + atomic_set(&q->bufs_queued, 0);
> + q->bufs_first = 0;
> + q->bufs_next = 0;
> +
> + return 0;
> +}
> +
> +/* Called after each buffer is allocated */
> +static int cio2_vb2_buf_init(struct vb2_buffer *vb)
> +{
> + struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue);
> + struct device *dev = &cio2->pci_dev->dev;
> + struct cio2_buffer *b =
> + container_of(vb, struct cio2_buffer, vbb.vb2_buf);
> + static const unsigned int entries_per_page =
> + CIO2_PAGE_SIZE / sizeof(u32);
> + unsigned int pages = DIV_ROUND_UP(vb->planes[0].length, CIO2_PAGE_SIZE);
> + unsigned int lops = DIV_ROUND_UP(pages + 1, entries_per_page);
> + struct sg_table *sg;
> + struct sg_page_iter sg_iter;
> + unsigned int i, j;
> +
> + if (lops <= 0 || lops > CIO2_MAX_LOPS) {
> + dev_err(dev, "%s: bad buffer size (%i)\n", __func__,
> + vb->planes[0].length);
> + return -ENOSPC; /* Should never happen */
> + }
> +
> + memset(b->lop, 0, sizeof(*b->lop));
> + /* Allocate LOP table */
> + for (i = 0; i < lops; i++) {
> + b->lop[i] = dma_alloc_coherent(dev, CIO2_PAGE_SIZE,
> + &b->lop_bus_addr[i], GFP_KERNEL);
> + if (!b->lop[i])
> + goto fail;
> + }
> +
> + /* Fill LOP */
> + sg = vb2_dma_sg_plane_desc(vb, 0);
> + if (!sg)
> + return -ENOMEM;
> +
> + if (sg->nents && sg->sgl)
> + b->offset = sg->sgl->offset;
> +
> + i = j = 0;
> + for_each_sg_page(sg->sgl, &sg_iter, sg->nents, 0) {
> + b->lop[i][j] = sg_page_iter_dma_address(&sg_iter) >> PAGE_SHIFT;
> + j++;
> + if (j == entries_per_page) {
> + i++;
> + j = 0;
> + }
> + }
> +
> + b->lop[i][j] = cio2->dummy_page_bus_addr >> PAGE_SHIFT;
> + return 0;
> +fail:
> + for (; i >= 0; i--)
> + dma_free_coherent(dev, CIO2_PAGE_SIZE,
> + b->lop[i], b->lop_bus_addr[i]);
> + return -ENOMEM;
> +}
> +
> +/* Transfer buffer ownership to cio2 */
> +static void cio2_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> + struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue);
> + struct cio2_queue *q =
> + container_of(vb->vb2_queue, struct cio2_queue, vbq);
> + struct cio2_buffer *b =
> + container_of(vb, struct cio2_buffer, vbb.vb2_buf);
> + struct cio2_fbpt_entry *entry;
> + unsigned long flags;
> + unsigned int i, j, next = q->bufs_next;
> + int bufs_queued = atomic_inc_return(&q->bufs_queued);
> + u32 fbpt_rp;
> +
> + dev_dbg(&cio2->pci_dev->dev, "queue buffer %d\n", vb->index);
> +
> + /*
> + * This code queues the buffer to the CIO2 DMA engine, which starts
> + * running once streaming has started. It is possible that this code
> + * gets pre-empted due to increased CPU load. Upon this, the driver
> + * does not get an opportunity to queue new buffers to the CIO2 DMA
> + * engine. When the DMA engine encounters an FBPT entry without the
> + * VALID bit set, the DMA engine halts, which requires a restart of
> + * the DMA engine and sensor, to continue streaming.
> + * This is not desired and is highly unlikely given that there are
> + * 32 FBPT entries that the DMA engine needs to process, to run into
> + * an FBPT entry, without the VALID bit set. We try to mitigate this
> + * by disabling interrupts for the duration of this queueing.
> + */
> + local_irq_save(flags);
> +
> + fbpt_rp = (readl(cio2->base + CIO2_REG_CDMARI(CIO2_DMA_CHAN))
> + >> CIO2_CDMARI_FBPT_RP_SHIFT)
> + & CIO2_CDMARI_FBPT_RP_MASK;
> +
> + /*
> + * fbpt_rp is the fbpt entry that the dma is currently working
> + * on, but since it could jump to next entry at any time,
> + * assume that we might already be there.
> + */
> + fbpt_rp = (fbpt_rp + 1) % CIO2_MAX_BUFFERS;
> +
> + if (bufs_queued <= 1 || fbpt_rp == next)
> + /* Buffers were drained */
> + next = (fbpt_rp + 1) % CIO2_MAX_BUFFERS;
> +
> + for (i = 0; i < CIO2_MAX_BUFFERS; i++) {
> + /*
> + * We have allocated CIO2_MAX_BUFFERS circularly for the
> + * hw, the user has requested N buffer queue. The driver
> + * ensures N <= CIO2_MAX_BUFFERS and guarantees that whenever
> + * user queues a buffer, there necessarily is a free buffer.
> + */
> + if (!q->bufs[next]) {
> + q->bufs[next] = b;
> + entry = &q->fbpt[next * CIO2_MAX_LOPS];
> + cio2_fbpt_entry_init_buf(cio2, b, entry);
> + local_irq_restore(flags);
> + q->bufs_next = (next + 1) % CIO2_MAX_BUFFERS;
> + for (j = 0; j < vb->num_planes; j++)
> + vb2_set_plane_payload(vb, j,
> + q->format.plane_fmt[j].sizeimage);
> + return;
> + }
> +
> + dev_dbg(&cio2->pci_dev->dev, "entry %i was full!\n", next);
> + next = (next + 1) % CIO2_MAX_BUFFERS;
> + }
> +
> + local_irq_restore(flags);
> + dev_err(&cio2->pci_dev->dev, "error: all cio2 entries were full!\n");
> + atomic_dec(&q->bufs_queued);
> + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
> +}
> +
> +/* Called when each buffer is freed */
> +static void cio2_vb2_buf_cleanup(struct vb2_buffer *vb)
> +{
> + struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue);
> + struct cio2_buffer *b =
> + container_of(vb, struct cio2_buffer, vbb.vb2_buf);
> + unsigned int i;
> +
> + /* Free LOP table */
> + for (i = 0; i < CIO2_MAX_LOPS; i++) {
> + if (b->lop[i])
> + dma_free_coherent(&cio2->pci_dev->dev, CIO2_PAGE_SIZE,
> + b->lop[i], b->lop_bus_addr[i]);
> + }
> +}
> +
> +static int cio2_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> + struct cio2_queue *q = vb2q_to_cio2_queue(vq);
> + struct cio2_device *cio2 = vb2_get_drv_priv(vq);
> + int r;
> +
> + cio2->cur_queue = q;
> + atomic_set(&q->frame_sequence, 0);
> +
> + r = pm_runtime_get_sync(&cio2->pci_dev->dev);
> + if (r < 0) {
> + dev_info(&cio2->pci_dev->dev, "failed to set power %d\n", r);
> + pm_runtime_put(&cio2->pci_dev->dev);
> + return r;
> + }
> +
> + r = media_pipeline_start(&q->vdev.entity, &q->pipe);
> + if (r)
> + goto fail_pipeline;
> +
> + r = cio2_hw_init(cio2, q);
> + if (r)
> + goto fail_hw;
> +
> + /* Start streaming on sensor */
> + r = v4l2_subdev_call(q->sensor, video, s_stream, 1);
> + if (r)
> + goto fail_csi2_subdev;
> +
> + cio2->streaming = true;
> +
> + return 0;
> +
> +fail_csi2_subdev:
> + cio2_hw_exit(cio2, q);
> +fail_hw:
> + media_pipeline_stop(&q->vdev.entity);
> +fail_pipeline:
> + dev_dbg(&cio2->pci_dev->dev, "failed to start streaming (%d)\n", r);
> + cio2_vb2_return_all_buffers(q);
> +
> + return r;
> +}
> +
> +static void cio2_vb2_stop_streaming(struct vb2_queue *vq)
> +{
> + struct cio2_queue *q = vb2q_to_cio2_queue(vq);
> + struct cio2_device *cio2 = vb2_get_drv_priv(vq);
> +
> + if (v4l2_subdev_call(q->sensor, video, s_stream, 0))
> + dev_err(&cio2->pci_dev->dev,
> + "failed to stop sensor streaming\n");
> +
> + cio2_hw_exit(cio2, q);
> + cio2_vb2_return_all_buffers(q);
> + media_pipeline_stop(&q->vdev.entity);
> + pm_runtime_put(&cio2->pci_dev->dev);
> + cio2->streaming = false;
> +}
> +
> +static const struct vb2_ops cio2_vb2_ops = {
> + .buf_init = cio2_vb2_buf_init,
> + .buf_queue = cio2_vb2_buf_queue,
> + .buf_cleanup = cio2_vb2_buf_cleanup,
> + .queue_setup = cio2_vb2_queue_setup,
> + .start_streaming = cio2_vb2_start_streaming,
> + .stop_streaming = cio2_vb2_stop_streaming,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> +};
> +
> +/**************** V4L2 interface ****************/
> +
> +static int cio2_v4l2_querycap(struct file *file, void *fh,
> + struct v4l2_capability *cap)
> +{
> + struct cio2_device *cio2 = video_drvdata(file);
> +
> + strlcpy(cap->driver, CIO2_NAME, sizeof(cap->driver));
> + strlcpy(cap->card, CIO2_DEVICE_NAME, sizeof(cap->card));
> + snprintf(cap->bus_info, sizeof(cap->bus_info),
> + "PCI:%s", pci_name(cio2->pci_dev));
> +
> + return 0;
> +}
> +
> +static int cio2_v4l2_enum_fmt(struct file *file, void *fh,
> + struct v4l2_fmtdesc *f)
> +{
> + if (f->index >= ARRAY_SIZE(formats))
> + return -EINVAL;
> +
> + f->pixelformat = formats[f->index].fourcc;
> +
> + return 0;
> +}
> +
> +/* The format is validated in cio2_video_link_validate() */
> +static int cio2_v4l2_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + struct cio2_queue *q = file_to_cio2_queue(file);
> +
> + f->fmt.pix_mp = q->format;
> +
> + return 0;
> +}
> +
> +static int cio2_v4l2_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + const struct ipu3_cio2_fmt *fmt;
> + struct v4l2_pix_format_mplane *mpix = &f->fmt.pix_mp;
> +
> + fmt = cio2_find_format(&mpix->pixelformat, NULL);
> + if (!fmt)
> + fmt = &formats[0];
> +
> + /* Only supports up to 4224x3136 */
> + if (mpix->width > CIO2_IMAGE_MAX_WIDTH)
> + mpix->width = CIO2_IMAGE_MAX_WIDTH;
> + if (mpix->height > CIO2_IMAGE_MAX_LENGTH)
> + mpix->height = CIO2_IMAGE_MAX_LENGTH;
> +
> + mpix->num_planes = 1;
> + mpix->pixelformat = fmt->fourcc;
> + mpix->colorspace = V4L2_COLORSPACE_RAW;
> + mpix->field = V4L2_FIELD_NONE;
> + memset(mpix->reserved, 0, sizeof(mpix->reserved));
> + mpix->plane_fmt[0].bytesperline = cio2_bytesperline(mpix->width);
> + mpix->plane_fmt[0].sizeimage = mpix->plane_fmt[0].bytesperline *
> + mpix->height;
> + memset(mpix->plane_fmt[0].reserved, 0,
> + sizeof(mpix->plane_fmt[0].reserved));
> +
> + /* use default */
> + mpix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + mpix->quantization = V4L2_QUANTIZATION_DEFAULT;
> + mpix->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> + return 0;
> +}
> +
> +static int cio2_v4l2_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + struct cio2_queue *q = file_to_cio2_queue(file);
> +
> + cio2_v4l2_try_fmt(file, fh, f);
> + q->format = f->fmt.pix_mp;
> +
> + return 0;
> +}
> +
> +static int
> +cio2_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
> +{
> + if (input->index > 0)
> + return -EINVAL;
> +
> + strlcpy(input->name, "camera", sizeof(input->name));
> + input->type = V4L2_INPUT_TYPE_CAMERA;
> +
> + return 0;
> +}
> +
> +static int
> +cio2_video_g_input(struct file *file, void *fh, unsigned int *input)
> +{
> + *input = 0;
> +
> + return 0;
> +}
> +
> +static int
> +cio2_video_s_input(struct file *file, void *fh, unsigned int input)
> +{
> + return input == 0 ? 0 : -EINVAL;
> +}
> +
> +static const struct v4l2_file_operations cio2_v4l2_fops = {
> + .owner = THIS_MODULE,
> + .unlocked_ioctl = video_ioctl2,
> + .open = v4l2_fh_open,
> + .release = vb2_fop_release,
> + .poll = vb2_fop_poll,
> + .mmap = vb2_fop_mmap,
> +};
> +
> +static const struct v4l2_ioctl_ops cio2_v4l2_ioctl_ops = {
> + .vidioc_querycap = cio2_v4l2_querycap,
> + .vidioc_enum_fmt_vid_cap_mplane = cio2_v4l2_enum_fmt,
> + .vidioc_g_fmt_vid_cap_mplane = cio2_v4l2_g_fmt,
> + .vidioc_s_fmt_vid_cap_mplane = cio2_v4l2_s_fmt,
> + .vidioc_try_fmt_vid_cap_mplane = cio2_v4l2_try_fmt,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_enum_input = cio2_video_enum_input,
> + .vidioc_g_input = cio2_video_g_input,
> + .vidioc_s_input = cio2_video_s_input,
> +};
> +
> +static int cio2_subdev_subscribe_event(struct v4l2_subdev *sd,
> + struct v4l2_fh *fh,
> + struct v4l2_event_subscription *sub)
> +{
> + if (sub->type != V4L2_EVENT_FRAME_SYNC)
> + return -EINVAL;
> +
> + /* Line number. For now only zero accepted. */
> + if (sub->id != 0)
> + return -EINVAL;
> +
> + return v4l2_event_subscribe(fh, sub, 0, NULL);
> +}
> +
> +static int cio2_subdev_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> +{
> + struct v4l2_mbus_framefmt fmt;
> + struct v4l2_mbus_framefmt *format;
> + static const u32 default_width = 1936;
> + static const u32 default_height = 1096;
> +
> + /* Initialize try_fmt */
> + format = v4l2_subdev_get_try_format(sd, fh->pad, CIO2_PAD_SINK);
> + fmt.width = default_width;
> + fmt.height = default_height;
> + fmt.code = formats[0].mbus_code;
> + fmt.field = V4L2_FIELD_NONE;
> + fmt.colorspace = V4L2_COLORSPACE_RAW;
> + fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> + fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
Could you make the assignments in variable initialisation? It'd be safer,
as all fields in a struct will be initialised this way.
> + *format = fmt;
> +
> + /* same as sink */
> + format = v4l2_subdev_get_try_format(sd, fh->pad, CIO2_PAD_SOURCE);
> + *format = fmt;
> +
> + return 0;
> +}
> +
> +/*
> + * cio2_subdev_get_fmt - Handle get format by pads subdev method
> + * @sd : pointer to v4l2 subdev structure
> + * @cfg: V4L2 subdev pad config
> + * @fmt: pointer to v4l2 subdev format structure
> + * return -EINVAL or zero on success
> + */
> +static int cio2_subdev_get_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct cio2_queue *q = container_of(sd, struct cio2_queue, subdev);
> + struct v4l2_subdev_format sensor_fmt;
> + int ret;
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> + fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
> + return 0;
> + }
> +
> + if (fmt->pad == CIO2_PAD_SINK) {
> + sensor_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + ret = v4l2_subdev_call(q->sensor, pad, get_fmt, NULL,
> + &sensor_fmt);
Hmm. This doesn't belong here. This sould be the format set on this device,
not on the sensor. sensor_fmt is also uninitialised. Use q->subdev_fmt
instead, as you do on set_fmt.
> + if (ret)
> + return ret;
> + /* update colorspace etc from sensor */
> + q->subdev_fmt.colorspace = sensor_fmt.format.colorspace;
> + q->subdev_fmt.ycbcr_enc = sensor_fmt.format.ycbcr_enc;
> + q->subdev_fmt.quantization = sensor_fmt.format.quantization;
> + q->subdev_fmt.xfer_func = sensor_fmt.format.xfer_func;
> + }
> +
> + fmt->format = q->subdev_fmt;
> +
> + return 0;
> +}
> +
> +/*
> + * cio2_subdev_set_fmt - Handle set format by pads subdev method
> + * @sd : pointer to v4l2 subdev structure
> + * @cfg: V4L2 subdev pad config
> + * @fmt: pointer to v4l2 subdev format structure
> + * return -EINVAL or zero on success
> + */
> +static int cio2_subdev_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct cio2_queue *q = container_of(sd, struct cio2_queue, subdev);
> +
> + /*
> + * Only allow setting sink pad format;
> + * source always propagates from sink
> + */
> + if (fmt->pad == CIO2_PAD_SOURCE)
> + return cio2_subdev_get_fmt(sd, cfg, fmt);
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
> + } else {
> + /* It's the sink, allow changing frame size */
> + q->subdev_fmt.width = fmt->format.width;
> + q->subdev_fmt.height = fmt->format.height;
> + q->subdev_fmt.code = fmt->format.code;
> + fmt->format = q->subdev_fmt;
> + }
> +
> + return 0;
> +}
> +
> +static int cio2_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index >= ARRAY_SIZE(formats))
> + return -EINVAL;
> +
> + code->code = formats[code->index].mbus_code;
> + return 0;
> +}
> +
> +static int cio2_subdev_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 cio2_video_link_validate(struct media_link *link)
> +{
> + struct video_device *vd = container_of(link->sink->entity,
> + struct video_device, entity);
> + struct cio2_queue *q = container_of(vd, struct cio2_queue, vdev);
> + struct cio2_device *cio2 = video_get_drvdata(vd);
> + struct v4l2_subdev_format source_fmt;
> + int ret;
> +
> + if (!media_entity_remote_pad(link->sink->entity->pads)) {
> + dev_info(&cio2->pci_dev->dev,
> + "video node %s pad not connected\n", vd->name);
> + return -ENOTCONN;
> + }
> +
> + ret = cio2_subdev_link_validate_get_format(link->source, &source_fmt);
> + if (ret < 0)
> + return 0;
> +
> + if (source_fmt.format.width != q->format.width ||
> + source_fmt.format.height != q->format.height) {
> + dev_err(&cio2->pci_dev->dev,
> + "Wrong width or height %ux%u (%ux%u expected)\n",
> + q->format.width, q->format.height,
> + source_fmt.format.width, source_fmt.format.height);
> + return -EINVAL;
> + }
> +
> + if (!cio2_find_format(&q->format.pixelformat, &source_fmt.format.code))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_core_ops cio2_subdev_core_ops = {
> + .subscribe_event = cio2_subdev_subscribe_event,
> + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +};
> +
> +static const struct v4l2_subdev_internal_ops cio2_subdev_internal_ops = {
> + .open = cio2_subdev_open,
> +};
> +
> +static const struct v4l2_subdev_pad_ops cio2_subdev_pad_ops = {
> + .link_validate = v4l2_subdev_link_validate_default,
> + .get_fmt = cio2_subdev_get_fmt,
> + .set_fmt = cio2_subdev_set_fmt,
> + .enum_mbus_code = cio2_subdev_enum_mbus_code,
> +};
> +
> +static const struct v4l2_subdev_ops cio2_subdev_ops = {
> + .core = &cio2_subdev_core_ops,
> + .pad = &cio2_subdev_pad_ops,
> +};
> +
> +/******* V4L2 sub-device asynchronous registration callbacks***********/
> +
> +struct sensor_async_subdev {
> + struct v4l2_async_subdev asd;
> + struct csi2_bus_info csi2;
> +};
> +
> +/* The .bound() notifier callback when a match is found */
> +static int cio2_notifier_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_subdev *asd)
> +{
> + struct cio2_device *cio2 = container_of(notifier,
> + struct cio2_device, notifier);
> + struct sensor_async_subdev *s_asd = container_of(asd,
> + struct sensor_async_subdev, asd);
> + struct cio2_queue *q;
> +
> + if (cio2->queue[s_asd->csi2.port].sensor)
> + return -EBUSY;
> +
> + q = &cio2->queue[s_asd->csi2.port];
> +
> + q->csi2 = s_asd->csi2;
> + q->sensor = sd;
> + q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port);
> +
> + return 0;
> +}
> +
> +/* The .unbind callback */
> +static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_subdev *asd)
> +{
> + struct cio2_device *cio2 = container_of(notifier,
> + struct cio2_device, notifier);
> + struct sensor_async_subdev *s_asd = container_of(asd,
> + struct sensor_async_subdev, asd);
> +
> + cio2->queue[s_asd->csi2.port].sensor = NULL;
> +}
> +
> +/* .complete() is called after all subdevices have been located */
> +static int cio2_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct cio2_device *cio2 = container_of(notifier, struct cio2_device,
> + notifier);
> + struct sensor_async_subdev *s_asd;
> + struct cio2_queue *q;
> + unsigned int i, pad;
> + int ret;
> +
> + for (i = 0; i < notifier->num_subdevs; i++) {
> + s_asd = container_of(cio2->notifier.subdevs[i],
> + struct sensor_async_subdev, asd);
> + q = &cio2->queue[s_asd->csi2.port];
> +
> + for (pad = 0; pad < q->sensor->entity.num_pads; pad++)
> + if (q->sensor->entity.pads[pad].flags &
> + MEDIA_PAD_FL_SOURCE)
> + break;
> +
> + if (pad == q->sensor->entity.num_pads) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed to find src pad for %s\n",
> + q->sensor->name);
> + return -ENXIO;
> + }
> +
> + ret = media_create_pad_link(
> + &q->sensor->entity, pad,
> + &q->subdev.entity, CIO2_PAD_SINK,
> + 0);
> + if (ret) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed to create link for %s\n",
> + cio2->queue[i].sensor->name);
> + return ret;
> + }
> + }
> +
> + return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations cio2_async_ops = {
> + .bound = cio2_notifier_bound,
> + .unbind = cio2_notifier_unbind,
> + .complete = cio2_notifier_complete,
> +};
> +
> +static int cio2_fwnode_parse(struct device *dev,
> + struct v4l2_fwnode_endpoint *vep,
> + struct v4l2_async_subdev *asd)
> +{
> + struct sensor_async_subdev *s_asd =
> + container_of(asd, struct sensor_async_subdev, asd);
> +
> + if (vep->bus_type != V4L2_MBUS_CSI2) {
> + dev_err(dev, "Only CSI2 bus type is currently supported\n");
> + return -EINVAL;
> + }
> +
> + s_asd->csi2.port = vep->base.port;
> + s_asd->csi2.lanes = vep->bus.mipi_csi2.num_data_lanes;
> +
> + return 0;
> +}
> +
> +static int cio2_notifier_init(struct cio2_device *cio2)
> +{
> + int ret;
> +
> + ret = v4l2_async_notifier_parse_fwnode_endpoints(
> + &cio2->pci_dev->dev, &cio2->notifier,
> + sizeof(struct sensor_async_subdev),
> + cio2_fwnode_parse);
> + if (ret < 0)
> + return ret;
> +
> + if (!cio2->notifier.num_subdevs)
> + return -ENODEV; /* no endpoint */
> +
> + cio2->notifier.ops = &cio2_async_ops;
> + ret = v4l2_async_notifier_register(&cio2->v4l2_dev, &cio2->notifier);
> + if (ret) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed to register async notifier : %d\n", ret);
> + v4l2_async_notifier_cleanup(&cio2->notifier);
> + }
> +
> + return ret;
> +}
> +
> +static void cio2_notifier_exit(struct cio2_device *cio2)
> +{
> + v4l2_async_notifier_unregister(&cio2->notifier);
> + v4l2_async_notifier_cleanup(&cio2->notifier);
> +}
> +
> +/**************** Queue initialization ****************/
> +static const struct media_entity_operations cio2_media_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static const struct media_entity_operations cio2_video_entity_ops = {
> + .link_validate = cio2_video_link_validate,
> +};
> +
> +static int cio2_queue_init(struct cio2_device *cio2, struct cio2_queue *q)
> +{
> + static const u32 default_width = 1936;
> + static const u32 default_height = 1096;
> + const struct ipu3_cio2_fmt dflt_fmt = formats[0];
> +
> + struct video_device *vdev = &q->vdev;
> + struct vb2_queue *vbq = &q->vbq;
> + struct v4l2_subdev *subdev = &q->subdev;
> + struct v4l2_mbus_framefmt *fmt;
> + int r;
> +
> + /* Initialize miscellaneous variables */
> + mutex_init(&q->lock);
> +
> + /* Initialize formats to default values */
> + fmt = &q->subdev_fmt;
> + fmt->width = default_width;
> + fmt->height = default_height;
> + fmt->code = dflt_fmt.mbus_code;
> + fmt->field = V4L2_FIELD_NONE;
> +
> + q->format.width = default_width;
> + q->format.height = default_height;
> + q->format.pixelformat = dflt_fmt.fourcc;
> + q->format.colorspace = V4L2_COLORSPACE_RAW;
> + q->format.field = V4L2_FIELD_NONE;
> + q->format.num_planes = 1;
> + q->format.plane_fmt[0].bytesperline =
> + cio2_bytesperline(q->format.width);
> + q->format.plane_fmt[0].sizeimage = q->format.plane_fmt[0].bytesperline *
> + q->format.height;
> +
> + /* Initialize fbpt */
> + r = cio2_fbpt_init(cio2, q);
> + if (r)
> + goto fail_fbpt;
> +
> + /* Initialize media entities */
> + q->subdev_pads[CIO2_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
> + MEDIA_PAD_FL_MUST_CONNECT;
> + q->subdev_pads[CIO2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> + subdev->entity.ops = &cio2_media_ops;
> + subdev->internal_ops = &cio2_subdev_internal_ops;
> + r = media_entity_pads_init(&subdev->entity, CIO2_PADS, q->subdev_pads);
> + if (r) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed initialize subdev media entity (%d)\n", r);
> + goto fail_subdev_media_entity;
> + }
> +
> + q->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
> + vdev->entity.ops = &cio2_video_entity_ops;
> + r = media_entity_pads_init(&vdev->entity, 1, &q->vdev_pad);
> + if (r) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed initialize videodev media entity (%d)\n", r);
> + goto fail_vdev_media_entity;
> + }
> +
> + /* Initialize subdev */
> + v4l2_subdev_init(subdev, &cio2_subdev_ops);
> + subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
> + subdev->owner = THIS_MODULE;
> + snprintf(subdev->name, sizeof(subdev->name),
> + CIO2_ENTITY_NAME ":%td", q - cio2->queue);
> + v4l2_set_subdevdata(subdev, cio2);
> + r = v4l2_device_register_subdev(&cio2->v4l2_dev, subdev);
> + if (r) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed initialize subdev (%d)\n", r);
> + goto fail_subdev;
> + }
> +
> + /* Initialize vbq */
> + vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> + vbq->io_modes = VB2_USERPTR | VB2_MMAP | VB2_DMABUF;
> + vbq->ops = &cio2_vb2_ops;
> + vbq->mem_ops = &vb2_dma_sg_memops;
> + vbq->buf_struct_size = sizeof(struct cio2_buffer);
> + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + vbq->min_buffers_needed = 1;
> + vbq->drv_priv = cio2;
> + vbq->lock = &q->lock;
> + r = vb2_queue_init(vbq);
> + if (r) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed to initialize videobuf2 queue (%d)\n", r);
> + goto fail_vbq;
> + }
> +
> + /* Initialize vdev */
> + snprintf(vdev->name, sizeof(vdev->name),
> + "%s:%td", CIO2_NAME, q - cio2->queue);
> + vdev->release = video_device_release_empty;
> + vdev->fops = &cio2_v4l2_fops;
> + vdev->ioctl_ops = &cio2_v4l2_ioctl_ops;
> + vdev->lock = &cio2->lock;
> + vdev->v4l2_dev = &cio2->v4l2_dev;
> + vdev->queue = &q->vbq;
> + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
> + video_set_drvdata(vdev, cio2);
> + r = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
> + if (r) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed to register video device (%d)\n", r);
> + goto fail_vdev;
> + }
> +
> + /* Create link from CIO2 subdev to output node */
> + r = media_create_pad_link(
> + &subdev->entity, CIO2_PAD_SOURCE, &vdev->entity, 0,
> + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> + if (r)
> + goto fail_link;
> +
> + return 0;
> +
> +fail_link:
> + video_unregister_device(&q->vdev);
> +fail_vdev:
> + vb2_queue_release(vbq);
> +fail_vbq:
> + v4l2_device_unregister_subdev(subdev);
> +fail_subdev:
> + media_entity_cleanup(&vdev->entity);
> +fail_vdev_media_entity:
> + media_entity_cleanup(&subdev->entity);
> +fail_subdev_media_entity:
> + cio2_fbpt_exit(q, &cio2->pci_dev->dev);
> +fail_fbpt:
> + mutex_destroy(&q->lock);
> +
> + return r;
> +}
> +
> +static void cio2_queue_exit(struct cio2_device *cio2, struct cio2_queue *q)
> +{
> + video_unregister_device(&q->vdev);
> + media_entity_cleanup(&q->vdev.entity);
> + vb2_queue_release(&q->vbq);
> + v4l2_device_unregister_subdev(&q->subdev);
> + media_entity_cleanup(&q->subdev.entity);
> + cio2_fbpt_exit(q, &cio2->pci_dev->dev);
> + mutex_destroy(&q->lock);
> +}
> +
> +static int cio2_queues_init(struct cio2_device *cio2)
> +{
> + int i, r;
> +
> + for (i = 0; i < CIO2_QUEUES; i++) {
> + r = cio2_queue_init(cio2, &cio2->queue[i]);
> + if (r)
> + break;
> + }
> +
> + if (i == CIO2_QUEUES)
> + return 0;
> +
> + for (i--; i >= 0; i--)
> + cio2_queue_exit(cio2, &cio2->queue[i]);
> +
> + return r;
> +}
> +
> +static void cio2_queues_exit(struct cio2_device *cio2)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < CIO2_QUEUES; i++)
> + cio2_queue_exit(cio2, &cio2->queue[i]);
> +}
> +
> +/**************** PCI interface ****************/
> +
> +static int cio2_pci_config_setup(struct pci_dev *dev)
> +{
> + u16 pci_command;
> + int r = pci_enable_msi(dev);
> +
> + if (r) {
> + dev_err(&dev->dev, "failed to enable MSI (%d)\n", r);
> + return r;
> + }
> +
> + pci_read_config_word(dev, PCI_COMMAND, &pci_command);
> + pci_command |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
> + PCI_COMMAND_INTX_DISABLE;
> + pci_write_config_word(dev, PCI_COMMAND, pci_command);
> +
> + return 0;
> +}
> +
> +static int cio2_pci_probe(struct pci_dev *pci_dev,
> + const struct pci_device_id *id)
> +{
> + struct cio2_device *cio2;
> + phys_addr_t phys;
> + void __iomem *const *iomap;
> + int r;
> +
> + cio2 = devm_kzalloc(&pci_dev->dev, sizeof(*cio2), GFP_KERNEL);
> + if (!cio2)
> + return -ENOMEM;
> + cio2->pci_dev = pci_dev;
> +
> + r = pcim_enable_device(pci_dev);
> + if (r) {
> + dev_err(&pci_dev->dev, "failed to enable device (%d)\n", r);
> + return r;
> + }
> +
> + dev_info(&pci_dev->dev, "device 0x%x (rev: 0x%x)\n",
> + pci_dev->device, pci_dev->revision);
> +
> + phys = pci_resource_start(pci_dev, CIO2_PCI_BAR);
> +
> + r = pcim_iomap_regions(pci_dev, 1 << CIO2_PCI_BAR, pci_name(pci_dev));
> + if (r) {
> + dev_err(&pci_dev->dev, "failed to remap I/O memory (%d)\n", r);
> + return -ENODEV;
> + }
> +
> + iomap = pcim_iomap_table(pci_dev);
> + if (!iomap) {
> + dev_err(&pci_dev->dev, "failed to iomap table\n");
> + return -ENODEV;
> + }
> +
> + cio2->base = iomap[CIO2_PCI_BAR];
> +
> + pci_set_drvdata(pci_dev, cio2);
> +
> + pci_set_master(pci_dev);
> +
> + r = pci_set_dma_mask(pci_dev, CIO2_DMA_MASK);
> + if (r) {
> + dev_err(&pci_dev->dev, "failed to set DMA mask (%d)\n", r);
> + return -ENODEV;
> + }
> +
> + r = cio2_pci_config_setup(pci_dev);
> + if (r)
> + return -ENODEV;
> +
> + r = cio2_fbpt_init_dummy(cio2);
> + if (r)
> + return r;
> +
> + mutex_init(&cio2->lock);
> +
> + cio2->media_dev.dev = &cio2->pci_dev->dev;
> + strlcpy(cio2->media_dev.model, CIO2_DEVICE_NAME,
> + sizeof(cio2->media_dev.model));
> + snprintf(cio2->media_dev.bus_info, sizeof(cio2->media_dev.bus_info),
> + "PCI:%s", pci_name(cio2->pci_dev));
> + cio2->media_dev.hw_revision = 0;
> +
> + media_device_init(&cio2->media_dev);
> + r = media_device_register(&cio2->media_dev);
> + if (r < 0)
> + goto fail_mutex_destroy;
> +
> + cio2->v4l2_dev.mdev = &cio2->media_dev;
> + r = v4l2_device_register(&pci_dev->dev, &cio2->v4l2_dev);
> + if (r) {
> + dev_err(&pci_dev->dev,
> + "failed to register V4L2 device (%d)\n", r);
> + goto fail_media_device_unregister;
> + }
> +
> + r = cio2_queues_init(cio2);
> + if (r)
> + goto fail_v4l2_device_unregister;
> +
> + /* Register notifier for subdevices we care */
> + r = cio2_notifier_init(cio2);
> + if (r)
> + goto fail_cio2_queue_exit;
> +
> + r = devm_request_irq(&pci_dev->dev, pci_dev->irq, cio2_irq,
> + IRQF_SHARED, CIO2_NAME, cio2);
> + if (r) {
> + dev_err(&pci_dev->dev, "failed to request IRQ (%d)\n", r);
> + goto fail;
> + }
> +
> + pm_runtime_put_noidle(&pci_dev->dev);
> + pm_runtime_allow(&pci_dev->dev);
> +
> + return 0;
> +
> +fail:
> + cio2_notifier_exit(cio2);
> +fail_cio2_queue_exit:
> + cio2_queues_exit(cio2);
> +fail_v4l2_device_unregister:
> + v4l2_device_unregister(&cio2->v4l2_dev);
> +fail_media_device_unregister:
> + media_device_unregister(&cio2->media_dev);
> + media_device_cleanup(&cio2->media_dev);
> +fail_mutex_destroy:
> + mutex_destroy(&cio2->lock);
> + cio2_fbpt_exit_dummy(cio2);
> +
> + return r;
> +}
> +
> +static void cio2_pci_remove(struct pci_dev *pci_dev)
> +{
> + struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
> + unsigned int i;
> +
> + cio2_notifier_exit(cio2);
> + cio2_fbpt_exit_dummy(cio2);
> + for (i = 0; i < CIO2_QUEUES; i++)
> + cio2_queue_exit(cio2, &cio2->queue[i]);
> + v4l2_device_unregister(&cio2->v4l2_dev);
> + media_device_unregister(&cio2->media_dev);
> + media_device_cleanup(&cio2->media_dev);
> + mutex_destroy(&cio2->lock);
> +}
> +
> +static int cio2_runtime_suspend(struct device *dev)
> +{
> + struct pci_dev *pci_dev = to_pci_dev(dev);
> + struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
> + void __iomem *const base = cio2->base;
> + u16 pm;
> +
> + writel(CIO2_D0I3C_I3, base + CIO2_REG_D0I3C);
> + dev_dbg(dev, "cio2 runtime suspend.\n");
> +
> + pci_read_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, &pm);
> + pm = (pm >> CIO2_PMCSR_D0D3_SHIFT) << CIO2_PMCSR_D0D3_SHIFT;
> + pm |= CIO2_PMCSR_D3;
> + pci_write_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, pm);
> +
> + return 0;
> +}
> +
> +static int cio2_runtime_resume(struct device *dev)
> +{
> + struct pci_dev *pci_dev = to_pci_dev(dev);
> + struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
> + void __iomem *const base = cio2->base;
> + u16 pm;
> +
> + writel(CIO2_D0I3C_RR, base + CIO2_REG_D0I3C);
> + dev_dbg(dev, "cio2 runtime resume.\n");
> +
> + pci_read_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, &pm);
> + pm = (pm >> CIO2_PMCSR_D0D3_SHIFT) << CIO2_PMCSR_D0D3_SHIFT;
> + pci_write_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, pm);
> +
> + return 0;
> +}
> +
> +static int cio2_suspend(struct device *dev)
> +{
> + struct pci_dev *pci_dev = to_pci_dev(dev);
> + struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
> + struct cio2_queue *q = cio2->cur_queue;
> +
> + dev_dbg(dev, "cio2 suspend\n");
> + if (!cio2->streaming)
> + return 0;
> +
> + /* Stop stream */
> + cio2_hw_exit(cio2, q);
> +
> + pm_runtime_force_suspend(dev);
> +
> + /*
> + * Upon resume, hw starts to process the fbpt entries from beginning,
> + * so relocate the queued buffs to the fbpt head before suspend.
> + */
> + cio2_fbpt_rearrange(cio2, q);
> + q->bufs_first = 0;
> + q->bufs_next = 0;
> +
> + return 0;
> +}
> +
> +static int cio2_resume(struct device *dev)
> +{
> + struct pci_dev *pci_dev = to_pci_dev(dev);
> + struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
> + int r = 0;
> + struct cio2_queue *q = cio2->cur_queue;
> +
> + dev_dbg(dev, "cio2 resume\n");
> + if (!cio2->streaming)
> + return 0;
> + /* Start stream */
> + r = pm_runtime_force_resume(&cio2->pci_dev->dev);
> + if (r < 0) {
> + dev_err(&cio2->pci_dev->dev,
> + "failed to set power %d\n", r);
> + return r;
> + }
> +
> + r = cio2_hw_init(cio2, q);
> + if (r)
> + dev_err(dev, "fail to init cio2 hw\n");
> +
> + return r;
> +}
> +
> +static const struct dev_pm_ops cio2_pm_ops = {
> + SET_RUNTIME_PM_OPS(&cio2_runtime_suspend, &cio2_runtime_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(&cio2_suspend, &cio2_resume)
> +};
> +
> +static const struct pci_device_id cio2_pci_id_table[] = {
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, CIO2_PCI_ID) },
> + { 0 }
> +};
> +
> +MODULE_DEVICE_TABLE(pci, cio2_pci_id_table);
> +
> +static struct pci_driver cio2_pci_driver = {
> + .name = CIO2_NAME,
> + .id_table = cio2_pci_id_table,
> + .probe = cio2_pci_probe,
> + .remove = cio2_pci_remove,
> + .driver = {
> + .pm = &cio2_pm_ops,
> + },
> +};
> +
> +module_pci_driver(cio2_pci_driver);
> +
> +MODULE_AUTHOR("Tuukka Toivonen <tuukka.toivonen@intel.com>");
> +MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
> +MODULE_AUTHOR("Jian Xu Zheng <jian.xu.zheng@intel.com>");
> +MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>");
> +MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("IPU3 CIO2 driver");
> diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h
> new file mode 100644
> index 000000000000..1a559990920f
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h
> @@ -0,0 +1,449 @@
> +/*
> + * Copyright (c) 2017 Intel Corporation.
> + *
> + * 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 __IPU3_CIO2_H
> +#define __IPU3_CIO2_H
> +
> +#define CIO2_NAME "ipu3-cio2"
> +#define CIO2_DEVICE_NAME "Intel IPU3 CIO2"
> +#define CIO2_ENTITY_NAME "ipu3-csi2"
> +#define CIO2_PCI_ID 0x9d32
> +#define CIO2_PCI_BAR 0
> +#define CIO2_DMA_MASK DMA_BIT_MASK(39)
> +#define CIO2_IMAGE_MAX_WIDTH 4224
> +#define CIO2_IMAGE_MAX_LENGTH 3136
> +
> +#define CIO2_IMAGE_MAX_WIDTH 4224
> +#define CIO2_IMAGE_MAX_LENGTH 3136
> +
> +/* 32MB = 8xFBPT_entry */
> +#define CIO2_MAX_LOPS 8
> +#define CIO2_MAX_BUFFERS (PAGE_SIZE / 16 / CIO2_MAX_LOPS)
> +
> +#define CIO2_PAD_SINK 0
> +#define CIO2_PAD_SOURCE 1
> +#define CIO2_PADS 2
> +
> +#define CIO2_NUM_DMA_CHAN 20
> +#define CIO2_NUM_PORTS 4 /* DPHYs */
> +
> +/* 1 for each sensor */
> +#define CIO2_QUEUES CIO2_NUM_PORTS
> +
> +/* Register and bit field definitions */
> +#define CIO2_REG_PIPE_BASE(n) ((n) * 0x0400) /* n = 0..3 */
> +#define CIO2_REG_CSIRX_BASE 0x000
> +#define CIO2_REG_MIPIBE_BASE 0x100
> +#define CIO2_REG_PIXELGEN_BAS 0x200
> +#define CIO2_REG_IRQCTRL_BASE 0x300
> +#define CIO2_REG_GPREG_BASE 0x1000
> +
> +/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_CSIRX_BASE */
> +#define CIO2_REG_CSIRX_ENABLE (CIO2_REG_CSIRX_BASE + 0x0)
> +#define CIO2_REG_CSIRX_NOF_ENABLED_LANES (CIO2_REG_CSIRX_BASE + 0x4)
> +#define CIO2_REG_CSIRX_SP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x10)
> +#define CIO2_REG_CSIRX_LP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x14)
> +#define CIO2_CSIRX_IF_CONFIG_FILTEROUT 0x00
> +#define CIO2_CSIRX_IF_CONFIG_FILTEROUT_VC_INACTIVE 0x01
> +#define CIO2_CSIRX_IF_CONFIG_PASS 0x02
> +#define CIO2_CSIRX_IF_CONFIG_FLAG_ERROR BIT(2)
> +#define CIO2_REG_CSIRX_STATUS (CIO2_REG_CSIRX_BASE + 0x18)
> +#define CIO2_REG_CSIRX_STATUS_DLANE_HS (CIO2_REG_CSIRX_BASE + 0x1c)
> +#define CIO2_CSIRX_STATUS_DLANE_HS_MASK 0xff
> +#define CIO2_REG_CSIRX_STATUS_DLANE_LP (CIO2_REG_CSIRX_BASE + 0x20)
> +#define CIO2_CSIRX_STATUS_DLANE_LP_MASK 0xffffff
> +/* Termination enable and settle in 0.0625ns units, lane=0..3 or -1 for clock */
> +#define CIO2_REG_CSIRX_DLY_CNT_TERMEN(lane) \
> + (CIO2_REG_CSIRX_BASE + 0x2c + 8 * (lane))
> +#define CIO2_REG_CSIRX_DLY_CNT_SETTLE(lane) \
> + (CIO2_REG_CSIRX_BASE + 0x30 + 8 * (lane))
> +/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_MIPIBE_BASE */
> +#define CIO2_REG_MIPIBE_ENABLE (CIO2_REG_MIPIBE_BASE + 0x0)
> +#define CIO2_REG_MIPIBE_STATUS (CIO2_REG_MIPIBE_BASE + 0x4)
> +#define CIO2_REG_MIPIBE_COMP_FORMAT(vc) \
> + (CIO2_REG_MIPIBE_BASE + 0x8 + 0x4 * (vc))
> +#define CIO2_REG_MIPIBE_FORCE_RAW8 (CIO2_REG_MIPIBE_BASE + 0x20)
> +#define CIO2_REG_MIPIBE_FORCE_RAW8_ENABLE BIT(0)
> +#define CIO2_REG_MIPIBE_FORCE_RAW8_USE_TYPEID BIT(1)
> +#define CIO2_REG_MIPIBE_FORCE_RAW8_TYPEID_SHIFT 2
> +
> +#define CIO2_REG_MIPIBE_IRQ_STATUS (CIO2_REG_MIPIBE_BASE + 0x24)
> +#define CIO2_REG_MIPIBE_IRQ_CLEAR (CIO2_REG_MIPIBE_BASE + 0x28)
> +#define CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD (CIO2_REG_MIPIBE_BASE + 0x68)
> +#define CIO2_MIPIBE_GLOBAL_LUT_DISREGARD 1
> +#define CIO2_REG_MIPIBE_PKT_STALL_STATUS (CIO2_REG_MIPIBE_BASE + 0x6c)
> +#define CIO2_REG_MIPIBE_PARSE_GSP_THROUGH_LP_LUT_REG_IDX \
> + (CIO2_REG_MIPIBE_BASE + 0x70)
> +#define CIO2_REG_MIPIBE_SP_LUT_ENTRY(vc) \
> + (CIO2_REG_MIPIBE_BASE + 0x74 + 4 * (vc))
> +#define CIO2_REG_MIPIBE_LP_LUT_ENTRY(m) /* m = 0..15 */ \
> + (CIO2_REG_MIPIBE_BASE + 0x84 + 4 * (m))
> +#define CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD 1
> +#define CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT 1
> +#define CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT 5
> +#define CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT 7
> +
> +/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_IRQCTRL_BASE */
> +/* IRQ registers are 18-bit wide, see cio2_irq_error for bit definitions */
> +#define CIO2_REG_IRQCTRL_EDGE (CIO2_REG_IRQCTRL_BASE + 0x00)
> +#define CIO2_REG_IRQCTRL_MASK (CIO2_REG_IRQCTRL_BASE + 0x04)
> +#define CIO2_REG_IRQCTRL_STATUS (CIO2_REG_IRQCTRL_BASE + 0x08)
> +#define CIO2_REG_IRQCTRL_CLEAR (CIO2_REG_IRQCTRL_BASE + 0x0c)
> +#define CIO2_REG_IRQCTRL_ENABLE (CIO2_REG_IRQCTRL_BASE + 0x10)
> +#define CIO2_REG_IRQCTRL_LEVEL_NOT_PULSE (CIO2_REG_IRQCTRL_BASE + 0x14)
> +
> +#define CIO2_REG_GPREG_SRST (CIO2_REG_GPREG_BASE + 0x0)
> +#define CIO2_GPREG_SRST_ALL 0xffff /* Reset all */
> +#define CIO2_REG_FB_HPLL_FREQ (CIO2_REG_GPREG_BASE + 0x08)
> +#define CIO2_REG_ISCLK_RATIO (CIO2_REG_GPREG_BASE + 0xc)
> +
> +#define CIO2_REG_CGC 0x1400
> +#define CIO2_CGC_CSI2_TGE BIT(0)
> +#define CIO2_CGC_PRIM_TGE BIT(1)
> +#define CIO2_CGC_SIDE_TGE BIT(2)
> +#define CIO2_CGC_XOSC_TGE BIT(3)
> +#define CIO2_CGC_MPLL_SHUTDOWN_EN BIT(4)
> +#define CIO2_CGC_D3I3_TGE BIT(5)
> +#define CIO2_CGC_CSI2_INTERFRAME_TGE BIT(6)
> +#define CIO2_CGC_CSI2_PORT_DCGE BIT(8)
> +#define CIO2_CGC_CSI2_DCGE BIT(9)
> +#define CIO2_CGC_SIDE_DCGE BIT(10)
> +#define CIO2_CGC_PRIM_DCGE BIT(11)
> +#define CIO2_CGC_ROSC_DCGE BIT(12)
> +#define CIO2_CGC_XOSC_DCGE BIT(13)
> +#define CIO2_CGC_FLIS_DCGE BIT(14)
> +#define CIO2_CGC_CLKGATE_HOLDOFF_SHIFT 20
> +#define CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT 24
> +#define CIO2_REG_D0I3C 0x1408
> +#define CIO2_D0I3C_I3 BIT(2) /* Set D0I3 */
> +#define CIO2_D0I3C_RR BIT(3) /* Restore? */
> +#define CIO2_REG_SWRESET 0x140c
> +#define CIO2_SWRESET_SWRESET 1
> +#define CIO2_REG_SENSOR_ACTIVE 0x1410
> +#define CIO2_REG_INT_STS 0x1414
> +#define CIO2_REG_INT_STS_EXT_OE 0x1418
> +#define CIO2_INT_EXT_OE_DMAOE_SHIFT 0
> +#define CIO2_INT_EXT_OE_DMAOE_MASK 0x7ffff
> +#define CIO2_INT_EXT_OE_OES_SHIFT 24
> +#define CIO2_INT_EXT_OE_OES_MASK (0xf << CIO2_INT_EXT_OE_OES_SHIFT)
> +#define CIO2_REG_INT_EN 0x1420
> +#define CIO2_REG_INT_EN_IRQ (1 << 24)
> +#define CIO2_REG_INT_EN_IOS(dma) (1 << (((dma) >> 1) + 12))
> +/*
> + * Interrupt on completion bit, Eg. DMA 0-3 maps to bit 0-3,
> + * DMA4 & DMA5 map to bit 4 ... DMA18 & DMA19 map to bit 11 Et cetera
> + */
> +#define CIO2_INT_IOC(dma) (1 << ((dma) < 4 ? (dma) : ((dma) >> 1) + 2))
> +#define CIO2_INT_IOC_SHIFT 0
> +#define CIO2_INT_IOC_MASK (0x7ff << CIO2_INT_IOC_SHIFT)
> +#define CIO2_INT_IOS_IOLN(dma) (1 << (((dma) >> 1) + 12))
> +#define CIO2_INT_IOS_IOLN_SHIFT 12
> +#define CIO2_INT_IOS_IOLN_MASK (0x3ff << CIO2_INT_IOS_IOLN_SHIFT)
> +#define CIO2_INT_IOIE BIT(22)
> +#define CIO2_INT_IOOE BIT(23)
> +#define CIO2_INT_IOIRQ BIT(24)
> +#define CIO2_REG_INT_EN_EXT_OE 0x1424
> +#define CIO2_REG_DMA_DBG 0x1448
> +#define CIO2_REG_DMA_DBG_DMA_INDEX_SHIFT 0
> +#define CIO2_REG_PBM_ARB_CTRL 0x1460
> +#define CIO2_PBM_ARB_CTRL_LANES_DIV 0 /* 4-4-2-2 lanes */
> +#define CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT 0
> +#define CIO2_PBM_ARB_CTRL_LE_EN BIT(7)
> +#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN 2
> +#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT 8
> +#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP 480
> +#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT 16
> +#define CIO2_REG_PBM_WMCTRL1 0x1464
> +#define CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT 0
> +#define CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT 8
> +#define CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT 16
> +#define CIO2_PBM_WMCTRL1_TS_COUNT_DISABLE BIT(31)
> +#define CIO2_PBM_WMCTRL1_MIN_2CK (4 << CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT)
> +#define CIO2_PBM_WMCTRL1_MID1_2CK (16 << CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT)
> +#define CIO2_PBM_WMCTRL1_MID2_2CK (21 << CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT)
> +#define CIO2_REG_PBM_WMCTRL2 0x1468
> +#define CIO2_PBM_WMCTRL2_HWM_2CK 40
> +#define CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT 0
> +#define CIO2_PBM_WMCTRL2_LWM_2CK 22
> +#define CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT 8
> +#define CIO2_PBM_WMCTRL2_OBFFWM_2CK 2
> +#define CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT 16
> +#define CIO2_PBM_WMCTRL2_TRANSDYN 1
> +#define CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT 24
> +#define CIO2_PBM_WMCTRL2_DYNWMEN BIT(28)
> +#define CIO2_PBM_WMCTRL2_OBFF_MEM_EN BIT(29)
> +#define CIO2_PBM_WMCTRL2_OBFF_CPU_EN BIT(30)
> +#define CIO2_PBM_WMCTRL2_DRAINNOW BIT(31)
> +#define CIO2_REG_PBM_TS_COUNT 0x146c
> +#define CIO2_REG_PBM_FOPN_ABORT 0x1474
> +/* below n = 0..3 */
> +#define CIO2_PBM_FOPN_ABORT(n) (0x1 << 8 * (n))
> +#define CIO2_PBM_FOPN_FORCE_ABORT(n) (0x2 << 8 * (n))
> +#define CIO2_PBM_FOPN_FRAMEOPEN(n) (0x8 << 8 * (n))
> +#define CIO2_REG_LTRCTRL 0x1480
> +#define CIO2_LTRCTRL_LTRDYNEN BIT(16)
> +#define CIO2_LTRCTRL_LTRSTABLETIME_SHIFT 8
> +#define CIO2_LTRCTRL_LTRSTABLETIME_MASK 0xff
> +#define CIO2_LTRCTRL_LTRSEL1S3 BIT(7)
> +#define CIO2_LTRCTRL_LTRSEL1S2 BIT(6)
> +#define CIO2_LTRCTRL_LTRSEL1S1 BIT(5)
> +#define CIO2_LTRCTRL_LTRSEL1S0 BIT(4)
> +#define CIO2_LTRCTRL_LTRSEL2S3 BIT(3)
> +#define CIO2_LTRCTRL_LTRSEL2S2 BIT(2)
> +#define CIO2_LTRCTRL_LTRSEL2S1 BIT(1)
> +#define CIO2_LTRCTRL_LTRSEL2S0 BIT(0)
> +#define CIO2_REG_LTRVAL23 0x1484
> +#define CIO2_REG_LTRVAL01 0x1488
> +#define CIO2_LTRVAL02_VAL_SHIFT 0
> +#define CIO2_LTRVAL02_SCALE_SHIFT 10
> +#define CIO2_LTRVAL13_VAL_SHIFT 16
> +#define CIO2_LTRVAL13_SCALE_SHIFT 26
> +
> +#define CIO2_LTRVAL0_VAL 175
> +/* Value times 1024 ns */
> +#define CIO2_LTRVAL0_SCALE 2
> +#define CIO2_LTRVAL1_VAL 90
> +#define CIO2_LTRVAL1_SCALE 2
> +#define CIO2_LTRVAL2_VAL 90
> +#define CIO2_LTRVAL2_SCALE 2
> +#define CIO2_LTRVAL3_VAL 90
> +#define CIO2_LTRVAL3_SCALE 2
> +
> +#define CIO2_REG_CDMABA(n) (0x1500 + 0x10 * (n)) /* n = 0..19 */
> +#define CIO2_REG_CDMARI(n) (0x1504 + 0x10 * (n))
> +#define CIO2_CDMARI_FBPT_RP_SHIFT 0
> +#define CIO2_CDMARI_FBPT_RP_MASK 0xff
> +#define CIO2_REG_CDMAC0(n) (0x1508 + 0x10 * (n))
> +#define CIO2_CDMAC0_FBPT_LEN_SHIFT 0
> +#define CIO2_CDMAC0_FBPT_WIDTH_SHIFT 8
> +#define CIO2_CDMAC0_FBPT_NS BIT(25)
> +#define CIO2_CDMAC0_DMA_INTR_ON_FS BIT(26)
> +#define CIO2_CDMAC0_DMA_INTR_ON_FE BIT(27)
> +#define CIO2_CDMAC0_FBPT_UPDATE_FIFO_FULL BIT(28)
> +#define CIO2_CDMAC0_FBPT_FIFO_FULL_FIX_DIS BIT(29)
> +#define CIO2_CDMAC0_DMA_EN BIT(30)
> +#define CIO2_CDMAC0_DMA_HALTED BIT(31)
> +#define CIO2_REG_CDMAC1(n) (0x150c + 0x10 * (n))
> +#define CIO2_CDMAC1_LINENUMINT_SHIFT 0
> +#define CIO2_CDMAC1_LINENUMUPDATE_SHIFT 16
> +/* n = 0..3 */
> +#define CIO2_REG_PXM_PXF_FMT_CFG0(n) (0x1700 + 0x30 * (n))
> +#define CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT 0
> +#define CIO2_PXM_PXF_FMT_CFG_SID1_SHIFT 16
> +#define CIO2_PXM_PXF_FMT_CFG_PCK_64B (0 << 0)
> +#define CIO2_PXM_PXF_FMT_CFG_PCK_32B (1 << 0)
> +#define CIO2_PXM_PXF_FMT_CFG_BPP_08 (0 << 2)
> +#define CIO2_PXM_PXF_FMT_CFG_BPP_10 (1 << 2)
> +#define CIO2_PXM_PXF_FMT_CFG_BPP_12 (2 << 2)
> +#define CIO2_PXM_PXF_FMT_CFG_BPP_14 (3 << 2)
> +#define CIO2_PXM_PXF_FMT_CFG_SPEC_4PPC (0 << 4)
> +#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_RGBA (1 << 4)
> +#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_ARGB (2 << 4)
> +#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR2 (3 << 4)
> +#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR3 (4 << 4)
> +#define CIO2_PXM_PXF_FMT_CFG_SPEC_NV16 (5 << 4)
> +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_AB (1 << 7)
> +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_CD (1 << 8)
> +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_AC (1 << 9)
> +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_BD (1 << 10)
> +#define CIO2_REG_INT_STS_EXT_IE 0x17e4
> +#define CIO2_REG_INT_EN_EXT_IE 0x17e8
> +#define CIO2_INT_EXT_IE_ECC_RE(n) (0x01 << (8 * (n)))
> +#define CIO2_INT_EXT_IE_DPHY_NR(n) (0x02 << (8 * (n)))
> +#define CIO2_INT_EXT_IE_ECC_NR(n) (0x04 << (8 * (n)))
> +#define CIO2_INT_EXT_IE_CRCERR(n) (0x08 << (8 * (n)))
> +#define CIO2_INT_EXT_IE_INTERFRAMEDATA(n) (0x10 << (8 * (n)))
> +#define CIO2_INT_EXT_IE_PKT2SHORT(n) (0x20 << (8 * (n)))
> +#define CIO2_INT_EXT_IE_PKT2LONG(n) (0x40 << (8 * (n)))
> +#define CIO2_INT_EXT_IE_IRQ(n) (0x80 << (8 * (n)))
> +#define CIO2_REG_PXM_FRF_CFG(n) (0x1720 + 0x30 * (n))
> +#define CIO2_PXM_FRF_CFG_FNSEL BIT(0)
> +#define CIO2_PXM_FRF_CFG_FN_RST BIT(1)
> +#define CIO2_PXM_FRF_CFG_ABORT BIT(2)
> +#define CIO2_PXM_FRF_CFG_CRC_TH_SHIFT 3
> +#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR BIT(8)
> +#define CIO2_PXM_FRF_CFG_MSK_ECC_RE BIT(9)
> +#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE BIT(10)
> +#define CIO2_PXM_FRF_CFG_EVEN_ODD_MODE_SHIFT 11
> +#define CIO2_PXM_FRF_CFG_MASK_CRC_THRES BIT(13)
> +#define CIO2_PXM_FRF_CFG_MASK_CSI_ACCEPT BIT(14)
> +#define CIO2_PXM_FRF_CFG_CIOHC_FS_MODE BIT(15)
> +#define CIO2_PXM_FRF_CFG_CIOHC_FRST_FRM_SHIFT 16
> +#define CIO2_REG_PXM_SID2BID0(n) (0x1724 + 0x30 * (n))
> +#define CIO2_FB_HPLL_FREQ 0x2
> +#define CIO2_ISCLK_RATIO 0xc
> +
> +#define CIO2_IRQCTRL_MASK 0x3ffff
> +
> +#define CIO2_INT_EN_EXT_OE_MASK 0x8f0fffff
> +
> +#define CIO2_CGC_CLKGATE_HOLDOFF 3
> +#define CIO2_CGC_CSI_CLKGATE_HOLDOFF 5
> +
> +#define CIO2_PXM_FRF_CFG_CRC_TH 16
> +
> +#define CIO2_INT_EN_EXT_IE_MASK 0xffffffff
> +
> +#define CIO2_DMA_CHAN 0
> +
> +#define CIO2_CSIRX_DLY_CNT_CLANE_IDX -1
> +
> +#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_A 0
> +#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_B 0
> +#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_A 95
> +#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_B -8
> +
> +#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_A 0
> +#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_B 0
> +#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_A 85
> +#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_B -2
> +
> +#define CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT 0x4
> +#define CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT 0x570
> +
> +#define CIO2_PMCSR_OFFSET 4
> +#define CIO2_PMCSR_D0D3_SHIFT 2
> +#define CIO2_PMCSR_D3 0x3
> +
> +struct cio2_csi2_timing {
> + s32 clk_termen;
> + s32 clk_settle;
> + s32 dat_termen;
> + s32 dat_settle;
> +};
> +
> +struct cio2_buffer {
> + struct vb2_v4l2_buffer vbb;
> + u32 *lop[CIO2_MAX_LOPS];
> + dma_addr_t lop_bus_addr[CIO2_MAX_LOPS];
> + unsigned int offset;
> +};
> +
> +struct csi2_bus_info {
> + u32 port;
> + u32 lanes;
> +};
> +
> +struct cio2_queue {
> + /* mutex to be used by vb2_queue */
> + struct mutex lock;
> + struct media_pipeline pipe;
> + struct csi2_bus_info csi2;
> + struct v4l2_subdev *sensor;
> + void __iomem *csi_rx_base;
> +
> + /* Subdev, /dev/v4l-subdevX */
> + struct v4l2_subdev subdev;
> + struct media_pad subdev_pads[CIO2_PADS];
> + struct v4l2_mbus_framefmt subdev_fmt;
> + atomic_t frame_sequence;
> +
> + /* Video device, /dev/videoX */
> + struct video_device vdev;
> + struct media_pad vdev_pad;
> + struct v4l2_pix_format_mplane format;
> + struct vb2_queue vbq;
> +
> + /* Buffer queue handling */
> + struct cio2_fbpt_entry *fbpt; /* Frame buffer pointer table */
> + dma_addr_t fbpt_bus_addr;
> + struct cio2_buffer *bufs[CIO2_MAX_BUFFERS];
> + unsigned int bufs_first; /* Index of the first used entry */
> + unsigned int bufs_next; /* Index of the first unused entry */
> + atomic_t bufs_queued;
> +};
> +
> +struct cio2_device {
> + struct pci_dev *pci_dev;
> + void __iomem *base;
> + struct v4l2_device v4l2_dev;
> + struct cio2_queue queue[CIO2_QUEUES];
> + struct cio2_queue *cur_queue;
> + /* mutex to be used by video_device */
> + struct mutex lock;
> +
> + bool streaming;
> + struct v4l2_async_notifier notifier;
> + struct media_device media_dev;
> +
> + /*
> + * Safety net to catch DMA fetch ahead
> + * when reaching the end of LOP
> + */
> + void *dummy_page;
> + /* DMA handle of dummy_page */
> + dma_addr_t dummy_page_bus_addr;
> + /* single List of Pointers (LOP) page */
> + u32 *dummy_lop;
> + /* DMA handle of dummy_lop */
> + dma_addr_t dummy_lop_bus_addr;
> +};
> +
> +/**************** Virtual channel ****************/
> +/*
> + * This should come from sensor driver. No
> + * driver interface nor requirement yet.
> + */
> +#define SENSOR_VIR_CH_DFLT 0
> +
> +/**************** FBPT operations ****************/
> +#define CIO2_FBPT_SIZE (CIO2_MAX_BUFFERS * CIO2_MAX_LOPS * \
> + sizeof(struct cio2_fbpt_entry))
> +
> +#define CIO2_FBPT_SUBENTRY_UNIT 4
> +#define CIO2_PAGE_SIZE PAGE_SIZE
> +
> +/* cio2 fbpt first_entry ctrl status */
> +#define CIO2_FBPT_CTRL_VALID BIT(0)
> +#define CIO2_FBPT_CTRL_IOC BIT(1)
> +#define CIO2_FBPT_CTRL_IOS BIT(2)
> +#define CIO2_FBPT_CTRL_SUCCXFAIL BIT(3)
> +#define CIO2_FBPT_CTRL_CMPLCODE_SHIFT 4
> +
> +/*
> + * Frame Buffer Pointer Table(FBPT) entry
> + * each entry describe an output buffer and consists of
> + * several sub-entries
> + */
> +struct __packed cio2_fbpt_entry {
> + union {
> + struct __packed {
> + u32 ctrl; /* status ctrl */
> + u16 cur_line_num; /* current line # written to DDR */
> + u16 frame_num; /* updated by DMA upon FE */
> + u32 first_page_offset; /* offset for 1st page in LOP */
> + } first_entry;
> + /* Second entry per buffer */
> + struct __packed {
> + u32 timestamp;
> + u32 num_of_bytes;
> + /* the number of bytes for write on last page */
> + u16 last_page_available_bytes;
> + /* the number of pages allocated for this buf */
> + u16 num_of_pages;
> + } second_entry;
> + };
> + u32 lop_page_addr; /* Points to list of pointers (LOP) table */
> +};
> +
> +static inline struct cio2_queue *file_to_cio2_queue(struct file *file)
> +{
> + return container_of(video_devdata(file), struct cio2_queue, vdev);
> +}
> +
> +static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq)
> +{
> + return container_of(vq, struct cio2_queue, vbq);
> +}
> +
> +#endif
--
Kind regards,
Sakari Ailus
e-mail: sakari.ailus@iki.fi
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2017-10-26 14:42 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-10-26 2:24 [PATCH v6 0/3] add IPU3 CIO2 CSI2 driver Yong Zhi
2017-10-26 2:24 ` [PATCH v6 1/3] videodev2.h, v4l2-ioctl: add IPU3 raw10 color format Yong Zhi
2017-10-26 2:24 ` [PATCH v6 2/3] doc-rst: add IPU3 raw10 bayer pixel format definitions Yong Zhi
2017-10-26 2:24 ` [PATCH v6 3/3] intel-ipu3: cio2: Add new MIPI-CSI2 driver Yong Zhi
2017-10-26 14:42 ` Sakari Ailus
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox